overlay

package
v1.15.0 Latest Latest
Warning

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

Go to latest
Published: Jan 19, 2026 License: MIT Imports: 17 Imported by: 1

README

OpenAPI

OpenAPI Overlay

An implementation of the OpenAPI Overlay Specification 1.1.0 for applying modifications to OpenAPI documents

Overlay reference Built by Speakeasy Release Go Doc
Go Report Card Software License

⚠️ This an alpha implementation. If you'd like to discuss a production use case please join the Speakeasy slack.

Features

  • OpenAPI Overlay Specification Compliance: Full implementation of the OpenAPI Overlay Specification 1.0.0 and 1.1.0
  • JSONPath Target Selection: Uses JSONPath expressions to select nodes for modification
  • RFC 9535 JSONPath: Version 1.1.0 uses RFC 9535-compliant JSONPath by default for improved interoperability
  • Remove, Update, and Copy Actions: Support for remove actions (pruning nodes), update actions (merging values), and copy actions (duplicating or moving nodes)
  • Upgrade Support: Built-in Upgrade() function to upgrade overlay documents from 1.0.0 to 1.1.0
  • Info Description Field: Version 1.1.0 supports a description field in the overlay info section
  • Flexible Input/Output: Works with both YAML and JSON formats
  • Batch Operations: Apply multiple modifications to large numbers of nodes in a single operation
  • YAML v1.2 Support: Uses gopkg.in/yaml.v3 for YAML v1.2 parsing (superset of JSON)

About OpenAPI Overlays

This specification defines a means of editing an OpenAPI Specification file by applying a list of actions. Each action is either a remove action that prunes nodes or an update that merges a value into nodes. The nodes impacted are selected by a target expression which uses JSONPath. This implementation supports version 1.1.0 which adds a copy action for duplicating or moving nodes within the document, RFC 9535 JSONPath as the default, and a description field in the info section.

The specification itself says very little about the input file to be modified or the output file. The presumed intention is that the input and output be an OpenAPI Specification, but that is not required.

In many ways, this is similar to JSONPatch, but without the requirement to use a single explicit path for each operation. This allows the creator of an overlay file to apply a single modification to a large number of nodes in the file within a single operation.

Apply an overlay to an OpenAPI document

Shows loading an overlay specification and applying it to transform an OpenAPI document.

overlayContent := `overlay: 1.0.0
info:
  title: Pet Store Enhancement Overlay
  version: 1.0.0
actions:
  - target: $.info.description
    update: Enhanced pet store API with additional features`

openAPIContent := `openapi: 3.1.0
info:
  title: Pet Store API
  version: 1.0.0
  description: A simple pet store API
paths:
  /pets:
    get:
      summary: List pets
      responses:
        '200':
          description: A list of pets`

overlayFile := "temp_overlay.yaml"
openAPIFile := "temp_openapi.yaml"
if err := os.WriteFile(overlayFile, []byte(overlayContent), 0644); err != nil {
	panic(err)
}
if err := os.WriteFile(openAPIFile, []byte(openAPIContent), 0644); err != nil {
	panic(err)
}
defer os.Remove(overlayFile)
defer os.Remove(openAPIFile)

overlayDoc, err := overlay.Parse(overlayFile)
if err != nil {
	panic(err)
}

openAPINode, err := loader.LoadSpecification(openAPIFile)
if err != nil {
	panic(err)
}

err = overlayDoc.ApplyTo(openAPINode)
if err != nil {
	panic(err)
}

// Convert back to YAML string
var buf strings.Builder
encoder := yaml.NewEncoder(&buf)
encoder.SetIndent(2)
err = encoder.Encode(openAPINode)
if err != nil {
	panic(err)
}

fmt.Printf("Transformed document:\n%s", buf.String())

Create an overlay specification programmatically

Shows building an overlay specification with update and remove actions.

// Create update value as yaml.Node
var updateNode yaml.Node
updateNode.SetString("Enhanced API with additional features")

overlayDoc := &overlay.Overlay{
	Version: "1.0.0",
	Info: overlay.Info{
		Title:   "API Enhancement Overlay",
		Version: "1.0.0",
	},
	Actions: []overlay.Action{
		{
			Target: "$.info.description",
			Update: updateNode,
		},
		{
			Target: "$.paths['/deprecated-endpoint']",
			Remove: true,
		},
	},
}

result, err := overlayDoc.ToString()
if err != nil {
	panic(err)
}

fmt.Printf("Overlay specification:\n%s", result)

Parse an overlay specification from a file

Shows loading an overlay file and accessing its properties.

overlayContent := `overlay: 1.0.0
info:
  title: API Modification Overlay
  version: 1.0.0
actions:
  - target: $.info.title
    update: Enhanced Pet Store API
  - target: $.info.version
    update: 2.0.0`

overlayFile := "temp_overlay.yaml"
if err := os.WriteFile(overlayFile, []byte(overlayContent), 0644); err != nil {
	panic(err)
}
defer func() { _ = os.Remove(overlayFile) }()

overlayDoc, err := overlay.Parse(overlayFile)
if err != nil {
	panic(err)
}

fmt.Printf("Overlay Version: %s\n", overlayDoc.Version)
fmt.Printf("Title: %s\n", overlayDoc.Info.Title)
fmt.Printf("Number of Actions: %d\n", len(overlayDoc.Actions))

for i, action := range overlayDoc.Actions {
	fmt.Printf("Action %d Target: %s\n", i+1, action.Target)
}

Validate an overlay specification

Shows loading and validating an overlay specification for correctness.

invalidOverlay := `overlay: 1.0.0
info:
  title: Invalid Overlay
actions:
  - target: $.info.title
    description: Missing update or remove`

overlayFile := "temp_invalid_overlay.yaml"
if err := os.WriteFile(overlayFile, []byte(invalidOverlay), 0644); err != nil {
	panic(err)
}
defer func() { _ = os.Remove(overlayFile) }()

overlayDoc, err := overlay.Parse(overlayFile)
if err != nil {
	fmt.Printf("Parse error: %s\n", err.Error())
	return
}

validationErr := overlayDoc.Validate()
if validationErr != nil {
	fmt.Println("Validation errors:")
	fmt.Printf("  %s\n", validationErr.Error())
} else {
	fmt.Println("Overlay specification is valid!")
}

Use remove actions in overlays

Shows removing specific paths and properties from an OpenAPI document.

openAPIContent := `openapi: 3.1.0
info:
  title: API
  version: 1.0.0
paths:
  /users:
    get:
      summary: List users
  /users/{id}:
    get:
      summary: Get user
  /admin:
    get:
      summary: Admin endpoint
      deprecated: true`

overlayContent := `overlay: 1.0.0
info:
  title: Cleanup Overlay
  version: 1.0.0
actions:
  - target: $.paths['/admin']
    remove: true`

openAPIFile := "temp_openapi.yaml"
overlayFile := "temp_overlay.yaml"
if err := os.WriteFile(openAPIFile, []byte(openAPIContent), 0644); err != nil {
	panic(err)
}
if err := os.WriteFile(overlayFile, []byte(overlayContent), 0644); err != nil {
	panic(err)
}
defer func() { _ = os.Remove(openAPIFile) }()
defer func() { _ = os.Remove(overlayFile) }()

overlayDoc, err := overlay.Parse(overlayFile)
if err != nil {
	panic(err)
}

openAPINode, err := loader.LoadSpecification(openAPIFile)
if err != nil {
	panic(err)
}

err = overlayDoc.ApplyTo(openAPINode)
if err != nil {
	panic(err)
}

var buf strings.Builder
encoder := yaml.NewEncoder(&buf)
encoder.SetIndent(2)
err = encoder.Encode(openAPINode)
if err != nil {
	panic(err)
}

fmt.Printf("Document after removing deprecated endpoint:\n%s", buf.String())

Contributing

This repository is maintained by Speakeasy, but we welcome and encourage contributions from the community to help improve its capabilities and stability.

How to Contribute

  1. Open Issues: Found a bug or have a feature suggestion? Open an issue to describe what you'd like to see changed.

  2. Pull Requests: We welcome pull requests! If you'd like to contribute code:

    • Fork the repository
    • Create a new branch for your feature/fix
    • Submit a PR with a clear description of the changes and any related issues
  3. Feedback: Share your experience using the packages or suggest improvements.

All contributions, whether they're bug reports, feature requests, or code changes, help make this project better for everyone.

Please ensure your contributions adhere to our coding standards and include appropriate tests where applicable.

Documentation

Overview

Example (Applying)

Example_applying demonstrates how to apply an overlay to an OpenAPI document. Shows loading an overlay specification and applying it to transform an OpenAPI document.

// Create temporary files for this example
overlayContent := `overlay: 1.0.0
info:
  title: Pet Store Enhancement Overlay
  version: 1.0.0
actions:
  - target: $.info.description
    update: Enhanced pet store API with additional features`

openAPIContent := `openapi: 3.1.0
info:
  title: Pet Store API
  version: 1.0.0
  description: A simple pet store API
paths:
  /pets:
    get:
      summary: List pets
      responses:
        '200':
          description: A list of pets`

// Write temporary files
overlayFile := "temp_overlay.yaml"
openAPIFile := "temp_openapi.yaml"
if err := os.WriteFile(overlayFile, []byte(overlayContent), 0644); err != nil {
	panic(err)
}
if err := os.WriteFile(openAPIFile, []byte(openAPIContent), 0644); err != nil {
	panic(err)
}
defer os.Remove(overlayFile)
defer os.Remove(openAPIFile)

// Parse the overlay
overlayDoc, err := overlay.Parse(overlayFile)
if err != nil {
	panic(err)
}

// Load the OpenAPI document
openAPINode, err := loader.LoadSpecification(openAPIFile)
if err != nil {
	panic(err)
}

// Apply the overlay to the OpenAPI document
err = overlayDoc.ApplyTo(openAPINode)
if err != nil {
	panic(err)
}

// Convert back to YAML string
var buf strings.Builder
encoder := yaml.NewEncoder(&buf)
encoder.SetIndent(2)
err = encoder.Encode(openAPINode)
if err != nil {
	panic(err)
}

fmt.Printf("Transformed document:\n%s", buf.String())
Output:

Transformed document:
openapi: 3.1.0
info:
  title: Pet Store API
  version: 1.0.0
  description: Enhanced pet store API with additional features
paths:
  /pets:
    get:
      summary: List pets
      responses:
        '200':
          description: A list of pets
Example (Creating)

Example_creating demonstrates how to create an overlay specification programmatically. Shows building an overlay specification with update and remove actions.

// Create update value as yaml.Node
var updateNode yaml.Node
updateNode.SetString("Enhanced API with additional features")

// Create an overlay with update and remove actions
overlayDoc := &overlay.Overlay{
	Version: "1.0.0",
	Info: overlay.Info{
		Title:   "API Enhancement Overlay",
		Version: "1.0.0",
	},
	Actions: []overlay.Action{
		{
			Target: "$.info.description",
			Update: updateNode,
		},
		{
			Target: "$.paths['/deprecated-endpoint']",
			Remove: true,
		},
	},
}

result, err := overlayDoc.ToString()
if err != nil {
	panic(err)
}

fmt.Printf("Overlay specification:\n%s", result)
Output:

Overlay specification:
overlay: 1.0.0
info:
  title: API Enhancement Overlay
  version: 1.0.0
actions:
  - target: $.info.description
    update: Enhanced API with additional features
  - target: $.paths['/deprecated-endpoint']
    remove: true
Example (Parsing)

Example_parsing demonstrates how to parse an overlay specification from a file. Shows loading an overlay file and accessing its properties.

package main

import (
	"fmt"
	"os"

	"github.com/speakeasy-api/openapi/overlay"
)

func main() {
	overlayContent := `overlay: 1.0.0
info:
  title: API Modification Overlay
  version: 1.0.0
actions:
  - target: $.info.title
    update: Enhanced Pet Store API
  - target: $.info.version
    update: 2.0.0`

	// Write temporary file
	overlayFile := "temp_overlay.yaml"
	if err := os.WriteFile(overlayFile, []byte(overlayContent), 0644); err != nil {
		panic(err)
	}
	defer func() { _ = os.Remove(overlayFile) }()

	overlayDoc, err := overlay.Parse(overlayFile)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Overlay Version: %s\n", overlayDoc.Version)
	fmt.Printf("Title: %s\n", overlayDoc.Info.Title)
	fmt.Printf("Number of Actions: %d\n", len(overlayDoc.Actions))

	for i, action := range overlayDoc.Actions {
		fmt.Printf("Action %d Target: %s\n", i+1, action.Target)
	}
}
Output:

Overlay Version: 1.0.0
Title: API Modification Overlay
Number of Actions: 2
Action 1 Target: $.info.title
Action 2 Target: $.info.version
Example (Removing)

Example_removing demonstrates how to use remove actions in overlays. Shows removing specific paths and properties from an OpenAPI document.

// Sample OpenAPI document with endpoints to remove
openAPIContent := `openapi: 3.1.0
info:
  title: API
  version: 1.0.0
paths:
  /users:
    get:
      summary: List users
  /users/{id}:
    get:
      summary: Get user
  /admin:
    get:
      summary: Admin endpoint
      deprecated: true`

// Overlay to remove deprecated endpoints
overlayContent := `overlay: 1.0.0
info:
  title: Cleanup Overlay
  version: 1.0.0
actions:
  - target: $.paths['/admin']
    remove: true`

// Write temporary files
openAPIFile := "temp_openapi.yaml"
overlayFile := "temp_overlay.yaml"
if err := os.WriteFile(openAPIFile, []byte(openAPIContent), 0644); err != nil {
	panic(err)
}
if err := os.WriteFile(overlayFile, []byte(overlayContent), 0644); err != nil {
	panic(err)
}
defer func() { _ = os.Remove(openAPIFile) }()
defer func() { _ = os.Remove(overlayFile) }()

overlayDoc, err := overlay.Parse(overlayFile)
if err != nil {
	panic(err)
}

openAPINode, err := loader.LoadSpecification(openAPIFile)
if err != nil {
	panic(err)
}

err = overlayDoc.ApplyTo(openAPINode)
if err != nil {
	panic(err)
}

var buf strings.Builder
encoder := yaml.NewEncoder(&buf)
encoder.SetIndent(2)
err = encoder.Encode(openAPINode)
if err != nil {
	panic(err)
}

fmt.Printf("Document after removing deprecated endpoint:\n%s", buf.String())
Output:

Document after removing deprecated endpoint:
openapi: 3.1.0
info:
  title: API
  version: 1.0.0
paths:
  /users:
    get:
      summary: List users
  /users/{id}:
    get:
      summary: Get user
Example (Validating)

Example_validating demonstrates how to validate an overlay specification. Shows loading and validating an overlay specification for correctness.

package main

import (
	"fmt"
	"os"

	"github.com/speakeasy-api/openapi/overlay"
)

func main() {
	// Invalid overlay specification (missing required fields)
	invalidOverlay := `overlay: 1.0.0
info:
  title: Invalid Overlay
actions:
  - target: $.info.title
    description: Missing update or remove`

	// Write temporary file
	overlayFile := "temp_invalid_overlay.yaml"
	if err := os.WriteFile(overlayFile, []byte(invalidOverlay), 0644); err != nil {
		panic(err)
	}
	defer func() { _ = os.Remove(overlayFile) }()

	overlayDoc, err := overlay.Parse(overlayFile)
	if err != nil {
		fmt.Printf("Parse error: %s\n", err.Error())
		return
	}

	validationErr := overlayDoc.Validate()
	if validationErr != nil {
		fmt.Println("Validation errors:")
		fmt.Printf("  %s\n", validationErr.Error())
	} else {
		fmt.Println("Overlay specification is valid!")
	}
}
Output:

Validation errors:
  overlay info version must be defined

Index

Examples

Constants

View Source
const (
	// LatestVersion is the latest supported overlay version
	LatestVersion = "1.1.0"
	// Version100 is the Overlay 1.0.0 version
	Version100 = "1.0.0"
	// Version110 is the Overlay 1.1.0 version
	Version110 = "1.1.0"
)

Version constants for the Overlay specification

View Source
const (
	// JSONPathRFC9535 enables RFC 9535 JSONPath implementation
	JSONPathRFC9535 = "rfc9535"
	// JSONPathLegacy enables legacy yamlpath implementation (for backward compatibility)
	JSONPathLegacy = "legacy"
)

JSONPath implementation constants

Variables

View Source
var (
	ErrOverlayVersionInvalid                   = errors.New("overlay version is invalid")
	ErrOverlayVersionNotSupported              = fmt.Errorf("overlay version must be one of: %s", strings.Join(sliceutil.Map(SupportedVersions, func(v *version.Version) string { return v.String() }), ", "))
	ErrOverlayVersionMustBeDefined             = errors.New("overlay version must be defined")
	ErrOverlayInfoTitleMustBeDefined           = errors.New("overlay info title must be defined")
	ErrOverlayInfoVersionMustBeDefined         = errors.New("overlay info version must be defined")
	ErrOverlayExtendsMustBeAValidURL           = errors.New("overlay extends must be a valid URL")
	ErrOverlayMustDefineAtLeastOneAction       = errors.New("overlay must define at least one action")
	ErrOverlayActionTargetMustBeDefined        = errors.New("overlay action target must be defined")
	ErrOverlayActionRemoveAndUpdateCannotBeSet = errors.New("overlay action remove and update cannot be set")
)

Errors

View Source
var (
	SupportedVersions = []*version.Version{version.MustParse("1.0.0"), version.MustParse("1.1.0")}
)

Functions

func Format

func Format(path string) error

Format will validate reformat the given file

func NewTargetSelector

func NewTargetSelector(path, method string) string

func Upgrade added in v1.15.0

func Upgrade(_ context.Context, o *Overlay, opts ...Option[UpgradeOptions]) (bool, error)

Upgrade upgrades an Overlay document from 1.0.0 to the latest version (1.1.0). Returns true if an upgrade was performed, false if no upgrade was needed.

The upgrade process:

  • Updates the overlay version field from 1.0.0 to 1.1.0
  • Enables RFC 9535 JSONPath as the default implementation
  • Clears redundant x-speakeasy-jsonpath: rfc9535 (now default in 1.1.0)
  • All existing actions remain valid and functional

Note: The upgrade is non-destructive. All 1.0.0 features continue to work in 1.1.0.

Types

type Action

type Action struct {
	Extensions `yaml:"-,inline"`

	// Target is the JSONPath to the target of the action.
	Target string `yaml:"target"`

	// Description is a description of the action.
	Description string `yaml:"description,omitempty"`

	// Update is the sub-document to use to merge or replace in the target. This is
	// ignored if Remove is set.
	Update yaml.Node `yaml:"update,omitempty"`

	// Remove marks the target node for removal rather than update.
	Remove bool `yaml:"remove,omitempty"`

	// Copy is a JSONPath to the source node to copy to the target. This is
	// mutually exclusive with Update and Remove.
	Copy string `yaml:"copy,omitempty"`
}

func NewUpdateAction

func NewUpdateAction(path, method string, update yaml.Node) Action

type Extensions

type Extensions map[string]any

Extensible provides a place for extensions to be added to components of the Overlay configuration. These are a map from x-* extension fields to their values.

type Info

type Info struct {
	Extensions `yaml:"-,inline"`

	// Title is the title of the overlay.
	Title string `yaml:"title"`

	// Version is the version of the overlay.
	Version string `yaml:"version"`

	// Description is an optional description of the overlay (new in Overlay 1.1.0).
	Description string `yaml:"description,omitempty"`
}

Info describes the metadata for the overlay.

type Option added in v1.15.0

type Option[T any] func(*T)

Option is a functional option for configuring behavior

func WithUpgradeTargetVersion added in v1.15.0

func WithUpgradeTargetVersion(ver string) Option[UpgradeOptions]

WithUpgradeTargetVersion sets the target version for the upgrade. If not specified, defaults to the latest supported version (1.1.0).

type Overlay

type Overlay struct {
	Extensions `yaml:"-,inline"`

	// Version is the version of the overlay configuration (1.0.0 or 1.1.0)
	Version string `yaml:"overlay"`

	// JSONPathVersion controls the JSONPath implementation used.
	// For version 1.0.0: default is legacy, use "rfc9535" to opt-in to RFC 9535
	// For version 1.1.0: default is RFC 9535, use "legacy" to opt-out
	JSONPathVersion string `yaml:"x-speakeasy-jsonpath,omitempty"`

	// Info describes the metadata for the overlay.
	Info Info `yaml:"info"`

	// Extends is a URL to the OpenAPI specification this overlay applies to.
	Extends string `yaml:"extends,omitempty"`

	// Actions is the list of actions to perform to apply the overlay.
	Actions []Action `yaml:"actions"`
}

Overlay is the top-level configuration for an OpenAPI overlay.

func Compare

func Compare(title string, y1 *yaml.Node, y2 yaml.Node) (*Overlay, error)

Compare compares input specifications from two files and returns an overlay that will convert the first into the second.

func Parse

func Parse(path string) (*Overlay, error)

Parse will parse the given reader as an overlay file.

func (*Overlay) ApplyTo

func (o *Overlay) ApplyTo(root *yaml.Node) error

ApplyTo will take an overlay and apply its changes to the given YAML document.

func (*Overlay) ApplyToStrict

func (o *Overlay) ApplyToStrict(root *yaml.Node) ([]string, error)

func (*Overlay) Format

func (o *Overlay) Format(w io.Writer) error

Format writes the file back out as YAML.

func (*Overlay) NewPath

func (o *Overlay) NewPath(target string, warnings *[]string) (Queryable, error)

NewPath creates a new JSONPath queryable from the given target expression. The implementation used depends on the overlay version and JSONPathVersion setting: - For version 1.0.0: Legacy yamlpath by default, opt-IN to RFC 9535 via "rfc9535" - For version 1.1.0+: RFC 9535 by default, opt-OUT to legacy via "legacy"

func (*Overlay) ToString

func (o *Overlay) ToString() (string, error)

func (*Overlay) UsesRFC9535

func (o *Overlay) UsesRFC9535() bool

UsesRFC9535 determines if the overlay should use RFC 9535 JSONPath implementation.

The behavior depends on the overlay version:

  • For version 1.0.x: RFC 9535 is opt-IN (default is legacy)
  • Set JSONPathVersion to "rfc9535" to enable RFC 9535
  • For version 1.1.0+: RFC 9535 is the DEFAULT (opt-OUT available)
  • Set JSONPathVersion to "legacy" to use legacy implementation

Explicit settings always take precedence over version-based defaults.

func (*Overlay) Validate

func (o *Overlay) Validate() error

func (*Overlay) ValidateVersion added in v1.12.1

func (o *Overlay) ValidateVersion() []error

type Queryable

type Queryable interface {
	Query(root *yaml.Node) []*yaml.Node
}

Queryable is an interface for querying YAML nodes using JSONPath expressions.

type UpgradeOptions added in v1.15.0

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

UpgradeOptions configures the upgrade behavior

type ValidationErrors

type ValidationErrors []error

func (ValidationErrors) Error

func (v ValidationErrors) Error() string

func (ValidationErrors) Return

func (v ValidationErrors) Return() error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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