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 with a config:
config := joiner.DefaultConfig()
config.PathStrategy = joiner.StrategyAcceptLeft
result, err := joiner.Join([]string{"base.yaml", "ext.yaml"}, config)
if err != nil {
log.Fatal(err)
}
_ = joiner.WriteResult(result, "merged.yaml")
Or create a reusable Joiner instance:
j := joiner.New(config)
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 $ref values are preserved but not merged across documents.
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 ¶
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
// contains filtered or unexported fields
}
JoinResult contains the joined OpenAPI specification and metadata
func Join ¶ added in v1.4.0
func Join(specPaths []string, config JoinerConfig) (*JoinResult, error)
Join is a convenience function that joins multiple OpenAPI specification files with the specified configuration. It's equivalent to creating a Joiner with New(), and calling Join().
For one-off join operations, this function provides a simpler API. For joining multiple sets of files with the same configuration, create a Joiner instance and reuse it.
Example:
config := joiner.DefaultConfig()
config.PathStrategy = joiner.StrategyAcceptLeft
result, err := joiner.Join([]string{"api1.yaml", "api2.yaml"}, config)
if err != nil {
log.Fatal(err)
}
func JoinParsed ¶ added in v1.4.0
func JoinParsed(parsedDocs []parser.ParseResult, config JoinerConfig) (*JoinResult, error)
JoinParsed is a convenience function that joins already-parsed OpenAPI specifications with the specified configuration.
Example:
doc1, _ := parser.Parse("api1.yaml", false, true)
doc2, _ := parser.Parse("api2.yaml", false, true)
result, err := joiner.JoinParsed([]parser.ParseResult{*doc1, *doc2}, joiner.DefaultConfig())
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