joiner

package
v1.19.1 Latest Latest
Warning

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

Go to latest
Published: Dec 7, 2025 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package joiner provides joining for multiple OpenAPI Specification documents.

The joiner merges multiple OAS documents of the same major version into a single document. It supports OAS 2.0 documents with other 2.0 documents, and all OAS 3.x versions together (3.0.x, 3.1.x, 3.2.x). It uses the version and format (JSON or YAML) from the first document as the result version and format, ensuring format consistency when writing output with WriteResult.

Quick Start

Join files using functional options:

result, err := joiner.JoinWithOptions(
	joiner.WithFilePaths([]string{"base.yaml", "ext.yaml"}),
	joiner.WithPathStrategy(joiner.StrategyAcceptLeft),
)
if err != nil {
	log.Fatal(err)
}
_ = joiner.WriteResult(result, "merged.yaml")

Or use a full config with options:

config := joiner.DefaultConfig()
config.PathStrategy = joiner.StrategyAcceptLeft
result, err := joiner.JoinWithOptions(
	joiner.WithFilePaths([]string{"base.yaml", "ext.yaml"}),
	joiner.WithConfig(config),
)

Or create a reusable Joiner instance:

j := joiner.New(joiner.DefaultConfig())
result1, _ := j.Join([]string{"api1-base.yaml", "api1-ext.yaml"})
result2, _ := j.Join([]string{"api2-base.yaml", "api2-ext.yaml"})
j.WriteResult(result1, "merged1.yaml")
j.WriteResult(result2, "merged2.yaml")

Collision Strategies

Control how collisions between documents are handled:

  • StrategyFailOnCollision: Fail on any collision (default)
  • StrategyAcceptLeft: Keep value from first document
  • StrategyAcceptRight: Keep value from last document
  • StrategyFailOnPaths: Fail only on path collisions, allow schema merging

Set strategies globally (DefaultStrategy) or per component type (PathStrategy, SchemaStrategy, ComponentStrategy). See the examples in example_test.go for configuration patterns.

Features and Limitations

The joiner validates all input documents, prevents output file overwrites with restrictive 0600 permissions, deduplicates tags, and optionally merges arrays (servers, security, tags). It uses the info object from the first document; subsequent info sections are ignored.

External References

The joiner preserves external $ref values but does NOT resolve or merge them. This is intentional to avoid ambiguity and maintain document structure.

If your documents contain external references, you have two options:

  1. Resolve references before joining: Use parser.ParseWithOptions(parser.WithResolveRefs(true)) before joining

  2. Keep external references and resolve after joining: Join the documents, then parse the result with WithResolveRefs(true)

Example with external references:

// Document 1: base.yaml
// paths:
//   /users:
//     get:
//       responses:
//         200:
//           schema:
//             $ref: "./schemas/user.yaml#/User"
//
// Document 2: extension.yaml
// paths:
//   /posts:
//     get:
//       responses:
//         200:
//           schema:
//             $ref: "./schemas/post.yaml#/Post"
//
// After joining, both $ref values are preserved in the merged document.
// Use parser.WithResolveRefs(true) to resolve them if needed.

The joiner integrates with other oastools packages:

Example

Example demonstrates basic usage of the joiner to combine two OpenAPI specifications.

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

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

func main() {
	outputPath := filepath.Join(os.TempDir(), "joined-example.yaml")
	defer func() { _ = os.Remove(outputPath) }()
	config := joiner.DefaultConfig()
	j := joiner.New(config)
	result, err := j.Join([]string{
		"../testdata/join-base-3.0.yaml",
		"../testdata/join-extension-3.0.yaml",
	})
	if err != nil {
		log.Fatalf("failed to join: %v", err)
	}
	err = j.WriteResult(result, outputPath)
	if err != nil {
		log.Fatalf("failed to write result: %v", err)
	}
	fmt.Printf("Version: %s\n", result.Version)
	fmt.Printf("Warnings: %d\n", len(result.Warnings))
}
Output:

Version: 3.0.3
Warnings: 0
Example (CustomStrategies)

Example_customStrategies demonstrates using custom collision strategies for different component types.

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

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

func main() {
	outputPath := filepath.Join(os.TempDir(), "joined-custom.yaml")
	defer func() { _ = os.Remove(outputPath) }()
	config := joiner.JoinerConfig{
		DefaultStrategy:   joiner.StrategyFailOnCollision,
		PathStrategy:      joiner.StrategyFailOnPaths,
		SchemaStrategy:    joiner.StrategyAcceptLeft,
		ComponentStrategy: joiner.StrategyAcceptRight,
		DeduplicateTags:   true,
		MergeArrays:       true,
	}
	j := joiner.New(config)
	result, err := j.Join([]string{
		"../testdata/join-base-3.0.yaml",
		"../testdata/join-extension-3.0.yaml",
	})
	if err != nil {
		log.Fatalf("failed to join: %v", err)
	}
	err = j.WriteResult(result, outputPath)
	if err != nil {
		log.Fatalf("failed to write result: %v", err)
	}
	fmt.Printf("Joined successfully\n")
	fmt.Printf("Collisions resolved: %d\n", result.CollisionCount)
}
Output:

Joined successfully
Collisions resolved: 0

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsValidStrategy

func IsValidStrategy(strategy string) bool

IsValidStrategy checks if a strategy string is valid

func ValidStrategies

func ValidStrategies() []string

ValidStrategies returns all valid collision strategy strings

Types

type CollisionError

type CollisionError struct {
	Section    string
	Key        string
	FirstFile  string
	FirstPath  string
	SecondFile string
	SecondPath string
	Strategy   CollisionStrategy
}

CollisionError provides detailed information about a collision

func (*CollisionError) Error

func (e *CollisionError) Error() string

type CollisionStrategy

type CollisionStrategy string

CollisionStrategy defines how to handle collisions when merging documents

const (
	// StrategyAcceptLeft keeps values from the first document when collisions occur
	StrategyAcceptLeft CollisionStrategy = "accept-left"
	// StrategyAcceptRight keeps values from the last document when collisions occur (overwrites)
	StrategyAcceptRight CollisionStrategy = "accept-right"
	// StrategyFailOnCollision returns an error if any collision is detected
	StrategyFailOnCollision CollisionStrategy = "fail"
	// StrategyFailOnPaths fails only on path collisions, allows schema/component collisions
	StrategyFailOnPaths CollisionStrategy = "fail-on-paths"
)

type JoinResult

type JoinResult struct {
	// Document contains the joined document (*parser.OAS2Document or *parser.OAS3Document)
	Document any
	// Version is the OpenAPI version of the joined document
	Version string
	// OASVersion is the enumerated version
	OASVersion parser.OASVersion
	// SourceFormat is the format of the first source file (JSON or YAML)
	SourceFormat parser.SourceFormat
	// Warnings contains non-fatal issues encountered during joining
	Warnings []string
	// CollisionCount tracks the number of collisions resolved
	CollisionCount int
	// Stats contains statistical information about the joined document
	Stats parser.DocumentStats
	// contains filtered or unexported fields
}

JoinResult contains the joined OpenAPI specification and metadata

func JoinWithOptions added in v1.11.0

func JoinWithOptions(opts ...Option) (*JoinResult, error)

JoinWithOptions joins multiple OpenAPI specifications using functional options. This provides a flexible, extensible API that combines input source selection and configuration in a single function call.

Example:

result, err := joiner.JoinWithOptions(
    joiner.WithFilePaths("api1.yaml", "api2.yaml"),
    joiner.WithPathStrategy(joiner.StrategyAcceptLeft),
)

type Joiner

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

Joiner handles joining of multiple OpenAPI specifications.

Concurrency: Joiner instances are not safe for concurrent use. Create separate Joiner instances for concurrent operations.

func New

func New(config JoinerConfig) *Joiner

New creates a new Joiner instance with the provided configuration

func (*Joiner) Join

func (j *Joiner) Join(specPaths []string) (*JoinResult, error)

Join joins multiple OpenAPI specifications into a single document

func (*Joiner) JoinParsed added in v1.3.1

func (j *Joiner) JoinParsed(parsedDocs []parser.ParseResult) (*JoinResult, error)

func (*Joiner) WriteResult

func (j *Joiner) WriteResult(result *JoinResult, outputPath string) error

WriteResult writes a join result to a file in YAML or JSON format (matching the source format)

The output file is written with restrictive permissions (0600 - owner read/write only) to protect potentially sensitive API specifications. If the file already exists, its permissions will be explicitly set to 0600 after writing.

type JoinerConfig

type JoinerConfig struct {
	// DefaultStrategy is the global strategy for all collisions
	DefaultStrategy CollisionStrategy
	// PathStrategy defines strategy specifically for path collisions
	PathStrategy CollisionStrategy
	// SchemaStrategy defines strategy specifically for schema/definition collisions
	SchemaStrategy CollisionStrategy
	// ComponentStrategy defines strategy for other component collisions (parameters, responses, etc.)
	ComponentStrategy CollisionStrategy
	// DeduplicateTags removes duplicate tags by name
	DeduplicateTags bool
	// MergeArrays determines whether to merge array fields (servers, security, etc.)
	MergeArrays bool
}

JoinerConfig configures how documents are joined

func DefaultConfig

func DefaultConfig() JoinerConfig

DefaultConfig returns a sensible default configuration

type Option added in v1.11.0

type Option func(*joinConfig) error

Option is a function that configures a join operation

func WithComponentStrategy added in v1.11.0

func WithComponentStrategy(strategy CollisionStrategy) Option

WithComponentStrategy sets the collision strategy for components

func WithConfig added in v1.11.0

func WithConfig(config JoinerConfig) Option

WithConfig applies an entire JoinerConfig struct This is useful for reusing existing configurations or loading from files

func WithDeduplicateTags added in v1.11.0

func WithDeduplicateTags(enabled bool) Option

WithDeduplicateTags enables or disables tag deduplication Default: true

func WithDefaultStrategy added in v1.11.0

func WithDefaultStrategy(strategy CollisionStrategy) Option

WithDefaultStrategy sets the global collision strategy

func WithFilePaths added in v1.11.0

func WithFilePaths(paths ...string) Option

WithFilePaths specifies file paths as input sources

func WithMergeArrays added in v1.11.0

func WithMergeArrays(enabled bool) Option

WithMergeArrays enables or disables array merging (servers, security, etc.) Default: true

func WithParsed added in v1.11.0

func WithParsed(docs ...parser.ParseResult) Option

WithParsed specifies parsed ParseResults as input sources

func WithPathStrategy added in v1.11.0

func WithPathStrategy(strategy CollisionStrategy) Option

WithPathStrategy sets the collision strategy for paths

func WithSchemaStrategy added in v1.11.0

func WithSchemaStrategy(strategy CollisionStrategy) Option

WithSchemaStrategy sets the collision strategy for schemas/definitions

Jump to

Keyboard shortcuts

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