Documentation
¶
Overview ¶
Package differ provides OpenAPI specification comparison and breaking change detection.
Overview ¶
The differ package enables comparison of OpenAPI specifications to identify differences, categorize changes, and detect breaking API changes. It supports both OAS 2.0 and OAS 3.x documents.
Usage ¶
The package provides two API styles:
- Package-level convenience functions for simple, one-off operations
- Struct-based API for reusable instances with custom configuration
Diff Modes ¶
The differ supports two operational modes:
- ModeSimple: Reports all semantic differences without categorization
- ModeBreaking: Categorizes changes by severity and identifies breaking changes
Change Categories ¶
Changes are categorized by the part of the specification that changed:
- CategoryEndpoint: Path/endpoint changes
- CategoryOperation: HTTP operation changes
- CategoryParameter: Parameter changes
- CategoryRequestBody: Request body changes
- CategoryResponse: Response changes
- CategorySchema: Schema/definition changes
- CategorySecurity: Security scheme changes
- CategoryServer: Server/host changes
- CategoryInfo: Metadata changes
Severity Levels ¶
In ModeBreaking, changes are assigned severity levels:
- SeverityCritical: Critical breaking changes (removed endpoints, operations)
- SeverityError: Breaking changes (removed required parameters, type changes)
- SeverityWarning: Potentially problematic changes (deprecated operations, new required fields)
- SeverityInfo: Non-breaking changes (additions, relaxed constraints)
Example (Simple Diff) ¶
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
)
func main() {
// Simple diff using convenience function
result, err := differ.Diff("api-v1.yaml", "api-v2.yaml")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d changes\n", len(result.Changes))
for _, change := range result.Changes {
fmt.Println(change.String())
}
}
Example (Breaking Change Detection) ¶
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
)
func main() {
// Create differ with breaking mode
d := differ.New()
d.Mode = differ.ModeBreaking
d.IncludeInfo = true
result, err := d.Diff("api-v1.yaml", "api-v2.yaml")
if err != nil {
log.Fatal(err)
}
if result.HasBreakingChanges {
fmt.Printf("⚠️ Found %d breaking change(s)!\n", result.BreakingCount)
}
fmt.Printf("Summary: %d breaking, %d warnings, %d info\n",
result.BreakingCount, result.WarningCount, result.InfoCount)
// Print changes grouped by severity
for _, change := range result.Changes {
fmt.Println(change.String())
}
}
Example (Reusable Differ Instance) ¶
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
)
func main() {
// Create a reusable differ instance
d := differ.New()
d.Mode = differ.ModeBreaking
d.IncludeInfo = false // Skip informational changes
// Compare multiple spec pairs with same configuration
pairs := []struct{ old, new string }{
{"api-v1.yaml", "api-v2.yaml"},
{"api-v2.yaml", "api-v3.yaml"},
{"api-v3.yaml", "api-v4.yaml"},
}
for _, pair := range pairs {
result, err := d.Diff(pair.old, pair.new)
if err != nil {
log.Printf("Error comparing %s to %s: %v", pair.old, pair.new, err)
continue
}
fmt.Printf("\n%s → %s:\n", pair.old, pair.new)
if result.HasBreakingChanges {
fmt.Printf(" ⚠️ %d breaking changes\n", result.BreakingCount)
} else {
fmt.Println(" ✓ No breaking changes")
}
}
}
Working with Parsed Documents ¶
For efficiency when documents are already parsed, use DiffParsed:
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
"github.com/erraggy/oastools/parser"
)
func main() {
// Parse documents once
source, err := parser.Parse("api-v1.yaml", false, true)
if err != nil {
log.Fatal(err)
}
target, err := parser.Parse("api-v2.yaml", false, true)
if err != nil {
log.Fatal(err)
}
// Compare parsed documents
result, err := differ.DiffParsed(*source, *target)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d changes\n", len(result.Changes))
}
Change Analysis ¶
The Change struct provides detailed information about each difference:
for _, change := range result.Changes {
fmt.Printf("Path: %s\n", change.Path)
fmt.Printf("Type: %s\n", change.Type)
fmt.Printf("Category: %s\n", change.Category)
fmt.Printf("Severity: %s\n", change.Severity)
fmt.Printf("Message: %s\n", change.Message)
if change.OldValue != nil {
fmt.Printf("Old value: %v\n", change.OldValue)
}
if change.NewValue != nil {
fmt.Printf("New value: %v\n", change.NewValue)
}
}
Breaking Change Examples ¶
Common breaking changes detected in ModeBreaking:
- Removed endpoints or operations (SeverityCritical)
- Removed required parameters (SeverityCritical)
- Changed parameter types (SeverityError)
- Made optional parameters required (SeverityError)
- Removed enum values (SeverityError)
- Removed success response codes (SeverityError)
- Removed schemas (SeverityError)
- Changed authentication requirements (SeverityError)
Non-Breaking Change Examples ¶
Common non-breaking changes in ModeBreaking:
- Added endpoints or operations (SeverityInfo)
- Added optional parameters (SeverityInfo)
- Made required parameters optional (SeverityInfo)
- Added enum values (SeverityInfo)
- Added response codes (SeverityInfo)
- Documentation updates (SeverityInfo)
Version Compatibility ¶
The differ works with:
- OAS 2.0 (Swagger) documents
- OAS 3.0.x documents
- OAS 3.1.x documents
- OAS 3.2.x documents
- Cross-version comparisons (with limitations)
When comparing documents of different OAS versions (e.g., 2.0 vs 3.0), the diff is limited to common elements present in both versions.
Example ¶
Example demonstrates basic diff usage with the convenience function
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
)
func main() {
// Compare two OpenAPI specifications
result, err := differ.Diff("../testdata/petstore-v1.yaml", "../testdata/petstore-v2.yaml")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d changes\n", len(result.Changes))
fmt.Printf("Source version: %s\n", result.SourceVersion)
fmt.Printf("Target version: %s\n", result.TargetVersion)
}
Example (Breaking) ¶
Example_breaking demonstrates breaking change detection
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
)
func main() {
d := differ.New()
d.Mode = differ.ModeBreaking
d.IncludeInfo = true
result, err := d.Diff("../testdata/petstore-v1.yaml", "../testdata/petstore-v2.yaml")
if err != nil {
log.Fatal(err)
}
if result.HasBreakingChanges {
fmt.Printf("⚠️ Found %d breaking change(s)\n", result.BreakingCount)
} else {
fmt.Println("✓ No breaking changes detected")
}
fmt.Printf("Summary: %d breaking, %d warnings, %d info\n",
result.BreakingCount, result.WarningCount, result.InfoCount)
}
Example (ChangeAnalysis) ¶
Example_changeAnalysis demonstrates detailed change analysis
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
)
func main() {
d := differ.New()
d.Mode = differ.ModeBreaking
result, err := d.Diff("../testdata/petstore-v1.yaml", "../testdata/petstore-v2.yaml")
if err != nil {
log.Fatal(err)
}
// Group changes by category
categories := make(map[differ.ChangeCategory]int)
for _, change := range result.Changes {
categories[change.Category]++
}
fmt.Println("Changes by category:")
for category, count := range categories {
fmt.Printf(" %s: %d\n", category, count)
}
}
Example (FilterBySeverity) ¶
Example_filterBySeverity demonstrates filtering changes by severity
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
)
func main() {
d := differ.New()
d.Mode = differ.ModeBreaking
d.IncludeInfo = false // Exclude info-level changes
result, err := d.Diff("../testdata/petstore-v1.yaml", "../testdata/petstore-v2.yaml")
if err != nil {
log.Fatal(err)
}
// Only breaking changes and warnings remain
fmt.Printf("Breaking and warnings only: %d changes\n", len(result.Changes))
fmt.Printf("Breaking: %d, Warnings: %d\n", result.BreakingCount, result.WarningCount)
}
Example (Parsed) ¶
Example_parsed demonstrates comparing already-parsed documents
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
"github.com/erraggy/oastools/parser"
)
func main() {
// Parse documents once
source, err := parser.Parse("../testdata/petstore-v1.yaml", false, true)
if err != nil {
log.Fatal(err)
}
target, err := parser.Parse("../testdata/petstore-v2.yaml", false, true)
if err != nil {
log.Fatal(err)
}
// Compare parsed documents
result, err := differ.DiffParsed(*source, *target)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d changes between %s and %s\n",
len(result.Changes), result.SourceVersion, result.TargetVersion)
}
Example (ReusableDiffer) ¶
Example_reusableDiffer demonstrates creating a reusable differ instance
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
)
func main() {
// Create a reusable differ with specific configuration
d := differ.New()
d.Mode = differ.ModeBreaking
d.IncludeInfo = false
d.UserAgent = "my-api-tool/1.0"
// Use the same differ for multiple comparisons
specs := []struct{ old, new string }{
{"../testdata/petstore-v1.yaml", "../testdata/petstore-v2.yaml"},
}
for _, spec := range specs {
result, err := d.Diff(spec.old, spec.new)
if err != nil {
log.Printf("Error: %v", err)
continue
}
fmt.Printf("%s → %s: ", spec.old, spec.new)
if result.HasBreakingChanges {
fmt.Printf("%d breaking\n", result.BreakingCount)
} else {
fmt.Println("compatible")
}
}
}
Example (Simple) ¶
Example_simple demonstrates simple diff mode
package main
import (
"fmt"
"log"
"github.com/erraggy/oastools/differ"
)
func main() {
d := differ.New()
d.Mode = differ.ModeSimple
result, err := d.Diff("../testdata/petstore-v1.yaml", "../testdata/petstore-v2.yaml")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Simple diff found %d changes\n", len(result.Changes))
// Print first few changes
for i, change := range result.Changes {
if i >= 3 {
break
}
fmt.Println(change.String())
}
}
Index ¶
Examples ¶
Constants ¶
const ( // SeverityInfo indicates informational changes (additions, relaxed constraints) SeverityInfo = severity.SeverityInfo // SeverityWarning indicates potentially problematic changes SeverityWarning = severity.SeverityWarning // SeverityError indicates breaking changes (removed features, stricter constraints) SeverityError = severity.SeverityError // SeverityCritical indicates critical breaking changes (removed endpoints, operations) SeverityCritical = severity.SeverityCritical )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Change ¶
type Change struct {
// Path is the JSON path to the changed element (e.g., "paths./pets.get")
Path string
// Type indicates if this is an addition, removal, or modification
Type ChangeType
// Category indicates which part of the spec was changed
Category ChangeCategory
// Severity indicates the impact level (only used in ModeBreaking)
Severity Severity
// OldValue is the value in the source document (nil for additions)
OldValue any
// NewValue is the value in the target document (nil for removals)
NewValue any
// Message is a human-readable description of the change
Message string
}
Change represents a single difference between two OpenAPI specifications
type ChangeCategory ¶
type ChangeCategory string
ChangeCategory indicates which part of the spec was changed
const ( // CategoryEndpoint indicates a path/endpoint change CategoryEndpoint ChangeCategory = "endpoint" // CategoryOperation indicates an HTTP operation change CategoryOperation ChangeCategory = "operation" // CategoryParameter indicates a parameter change CategoryParameter ChangeCategory = "parameter" // CategoryRequestBody indicates a request body change CategoryRequestBody ChangeCategory = "request_body" // CategoryResponse indicates a response change CategoryResponse ChangeCategory = "response" // CategorySchema indicates a schema/definition change CategorySchema ChangeCategory = "schema" // CategorySecurity indicates a security scheme change CategorySecurity ChangeCategory = "security" // CategoryServer indicates a server change CategoryServer ChangeCategory = "server" // CategoryInfo indicates metadata change (info, contact, license, etc.) CategoryInfo ChangeCategory = "info" )
type ChangeType ¶
type ChangeType string
ChangeType indicates whether a change is an addition, removal, or modification
const ( // ChangeTypeAdded indicates a new element was added ChangeTypeAdded ChangeType = "added" // ChangeTypeRemoved indicates an element was removed ChangeTypeRemoved ChangeType = "removed" // ChangeTypeModified indicates an existing element was changed ChangeTypeModified ChangeType = "modified" )
type DiffResult ¶
type DiffResult struct {
// SourceVersion is the source document's OAS version string
SourceVersion string
// SourceOASVersion is the enumerated source OAS version
SourceOASVersion parser.OASVersion
// TargetVersion is the target document's OAS version string
TargetVersion string
// TargetOASVersion is the enumerated target OAS version
TargetOASVersion parser.OASVersion
// Changes contains all detected changes
Changes []Change
// BreakingCount is the number of breaking changes (Critical + Error severity)
BreakingCount int
// WarningCount is the number of warnings
WarningCount int
// InfoCount is the number of informational changes
InfoCount int
// HasBreakingChanges is true if any breaking changes were detected
HasBreakingChanges bool
}
DiffResult contains the results of comparing two OpenAPI specifications
func Diff ¶
func Diff(sourcePath, targetPath string) (*DiffResult, error)
Diff is a convenience function that compares two OpenAPI specification files. It's equivalent to creating a Differ with New() and calling Diff().
For one-off diff operations, this function provides a simpler API. For comparing multiple files with the same configuration, create a Differ instance and reuse it.
Example:
result, err := differ.Diff("api-v1.yaml", "api-v2.yaml")
if err != nil {
log.Fatal(err)
}
if result.HasBreakingChanges {
// Handle breaking changes
}
func DiffParsed ¶
func DiffParsed(source, target parser.ParseResult) (*DiffResult, error)
DiffParsed is a convenience function that compares two already-parsed OpenAPI specifications.
Example:
source, _ := parser.Parse("api-v1.yaml", false, true)
target, _ := parser.Parse("api-v2.yaml", false, true)
result, err := differ.DiffParsed(*source, *target)
type Differ ¶
type Differ struct {
// Mode determines the type of diff operation (Simple or Breaking)
Mode DiffMode
// IncludeInfo determines whether to include informational changes
IncludeInfo bool
// UserAgent is the User-Agent string used when fetching URLs
// Defaults to "oastools" if not set
UserAgent string
}
Differ handles OpenAPI specification comparison
func (*Differ) Diff ¶
func (d *Differ) Diff(sourcePath, targetPath string) (*DiffResult, error)
Diff compares two OpenAPI specification files
func (*Differ) DiffParsed ¶
func (d *Differ) DiffParsed(source, target parser.ParseResult) (*DiffResult, error)
DiffParsed compares two already-parsed OpenAPI specifications