differ

package
v1.49.0 Latest Latest
Warning

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

Go to latest
Published: Feb 11, 2026 License: MIT Imports: 7 Imported by: 0

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:

  1. Package-level convenience functions for simple, one-off operations
  2. 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 functional options
	result, err := differ.DiffWithOptions(
		differ.WithSourceFilePath("api-v1.yaml"),
		differ.WithTargetFilePath("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() {
	// Diff with breaking mode using functional options
	result, err := differ.DiffWithOptions(
		differ.WithSourceFilePath("api-v1.yaml"),
		differ.WithTargetFilePath("api-v2.yaml"),
		differ.WithMode(differ.ModeBreaking),
		differ.WithIncludeInfo(true),
	)
	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 WithSourceParsed and WithTargetParsed:

package main

import (
	"fmt"
	"log"

	"github.com/erraggy/oastools/differ"
	"github.com/erraggy/oastools/parser"
)

func main() {
	// Parse documents once
	source, err := parser.ParseWithOptions(
		parser.WithFilePath("api-v1.yaml"),
		parser.WithValidateStructure(true),
	)
	if err != nil {
		log.Fatal(err)
	}

	target, err := parser.ParseWithOptions(
		parser.WithFilePath("api-v2.yaml"),
		parser.WithValidateStructure(true),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Compare parsed documents
	result, err := differ.DiffWithOptions(
		differ.WithSourceParsed(*source),
		differ.WithTargetParsed(*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)
	}
}

Pipeline Workflows with ToParseResult

The DiffResult.ToParseResult() method enables pipeline workflows where the target (newer) document continues through the processing chain. In a diff comparison, the "right" side is the target document - the one being compared against the baseline. ToParseResult returns this target document as a ParseResult.

This design supports workflows like: diff(v1, v2) → validate(v2) → publish

package main

import (
	"fmt"
	"log"

	"github.com/erraggy/oastools/differ"
	"github.com/erraggy/oastools/parser"
	"github.com/erraggy/oastools/validator"
)

func main() {
	// Parse both versions
	v1, _ := parser.ParseWithOptions(parser.WithFilePath("api-v1.yaml"))
	v2, _ := parser.ParseWithOptions(parser.WithFilePath("api-v2.yaml"))

	// Compare and detect breaking changes
	diffResult, _ := differ.DiffWithOptions(
		differ.WithSourceParsed(*v1),
		differ.WithTargetParsed(*v2),
		differ.WithMode(differ.ModeBreaking),
	)

	if diffResult.HasBreakingChanges {
		fmt.Printf("WARNING: %d breaking changes detected\n", diffResult.BreakingCount)
	}

	// Continue pipeline with the target (v2) document
	parseResult := diffResult.ToParseResult()

	// Validate the new version before publishing
	validationResult, err := validator.ValidateWithOptions(
		validator.WithParsed(*parseResult),
	)
	if err != nil {
		log.Fatal(err)
	}
	if !validationResult.Valid {
		log.Fatal("v2 has validation errors")
	}

	// Changes from diff are included as warnings for visibility
	fmt.Printf("Diff detected %d changes (now in warnings)\n", len(parseResult.Warnings))
}

Key points about ToParseResult:

  • Returns the TARGET document (the newer version in a comparison)
  • Changes are converted to warnings with severity prefixes for filtering
  • Falls back to "differ" as SourcePath if target had no path
  • Preserves all target document metadata (version, stats, format)

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.

Coverage Details

The differ provides comprehensive comparison of OpenAPI specification elements:

Response Comparison

Response objects are fully compared including:

  • Headers: All header properties (description, required, deprecated, type, style, schema)
  • Content/MediaTypes: Media type objects and their schemas
  • Links: Link objects (operationRef, operationId, description)
  • Examples: Example map keys (not deep value comparison)
  • Extensions: All x-* fields on Response objects

Header comparison includes:

  • Description and deprecation status
  • Required flag changes
  • Type and style modifications
  • Schema changes (delegates to schema comparison)
  • Extensions on Header objects

MediaType comparison includes:

  • Schema changes (delegates to comprehensive schema comparison)
  • Extensions on MediaType objects

Link comparison includes:

  • Operation references (operationRef, operationId)
  • Description changes
  • Extensions on Link objects

Schema Comparison

Schema objects are comprehensively compared including all fields:

Metadata:

  • title, description

Type information:

  • type, format

Numeric constraints:

  • multipleOf, maximum, exclusiveMaximum, minimum, exclusiveMinimum

String constraints:

  • maxLength, minLength, pattern

Array constraints:

  • maxItems, minItems, uniqueItems

Object constraints:

  • maxProperties, minProperties
  • required fields (with smart severity: adding required=ERROR, removing=INFO)

OAS-specific fields:

  • nullable, readOnly, writeOnly, deprecated

Schema comparison uses smart severity assignment in breaking mode:

  • ERROR: Stricter constraints (adding required fields, lowering max values, raising min values)
  • WARNING: Changes that might affect consumers (type changes, constraint modifications)
  • INFO: Relaxations and non-breaking changes (removing required, raising max, lowering min)

Note: Recursive schema properties (properties, items, allOf, oneOf, anyOf, not) are compared separately to avoid cyclic comparison issues.

Extension (x-*) Field Coverage

The OpenAPI Specification allows custom extension fields (starting with "x-") at many levels of the document. The differ detects and reports changes to extensions at commonly-used locations:

Extensions ARE diffed for these types:

  • Document level (OAS2Document, OAS3Document)
  • Info object
  • Server objects
  • PathItem objects
  • Operation objects
  • Parameter objects
  • RequestBody objects
  • Response objects
  • Header objects (response headers)
  • Link objects (response links)
  • MediaType objects (content types)
  • Schema objects
  • SecurityScheme objects
  • Tag objects
  • Components object

Extensions are NOT currently diffed for these less commonly-used types:

  • Contact, License, ExternalDocs (nested within Info)
  • ServerVariable (nested within Server)
  • Reference objects
  • Items (OAS 2.0 array item definitions)
  • Example, Encoding (response-related nested objects)
  • Discriminator, XML (schema-related nested objects)
  • OAuthFlows, OAuthFlow (security-related nested objects)

The rationale for this selective coverage is that extensions are most commonly placed at document, path, operation, parameter, response, and schema levels where they provide cross-cutting metadata. Extensions in deeply nested objects like ServerVariable, Discriminator, or Example are rare in practice.

If your use case requires extension diffing for the uncovered types, please open an issue at https://github.com/erraggy/oastools/issues

All extension changes are reported with CategoryExtension and are assigned SeverityInfo in breaking mode, as specification extensions are non-normative and optional according to the OpenAPI Specification.

The differ integrates with other oastools packages:

Example

Example demonstrates basic diff usage with functional options

package main

import (
	"fmt"
	"log"

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

func main() {
	// Compare two OpenAPI specifications
	result, err := differ.DiffWithOptions(
		differ.WithSourceFilePath("../testdata/petstore-v1.yaml"),
		differ.WithTargetFilePath("../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() {
	result, err := differ.DiffWithOptions(
		differ.WithSourceFilePath("../testdata/petstore-v1.yaml"),
		differ.WithTargetFilePath("../testdata/petstore-v2.yaml"),
		differ.WithMode(differ.ModeBreaking),
		differ.WithIncludeInfo(true),
	)
	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 (BreakingChanges)

Example_breakingChanges demonstrates how to detect breaking changes between two API versions and interpret the results by severity.

package main

import (
	"fmt"
	"log"

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

func main() {
	// Compare two API versions with breaking mode enabled
	result, err := differ.DiffWithOptions(
		differ.WithSourceFilePath("../testdata/petstore-v1.yaml"),
		differ.WithTargetFilePath("../testdata/petstore-v2.yaml"),
		differ.WithMode(differ.ModeBreaking),
		differ.WithIncludeInfo(true), // Include all change levels
	)

	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Comparing %s to %s\n", result.SourceVersion, result.TargetVersion)
	fmt.Printf("Total changes: %d\n\n", len(result.Changes))

	// Critical changes: API consumers WILL break
	// Examples: removed endpoints, required parameters, response schema changes
	criticalCount := 0
	errorCount := 0
	warningCount := 0
	infoCount := 0

	for _, change := range result.Changes {
		// Severity constants from internal/severity package:
		// SeverityCritical = 3, SeverityError = 2, SeverityWarning = 1, SeverityInfo = 0
		switch change.Severity {
		case 3: // Critical
			criticalCount++
			fmt.Printf("CRITICAL [%s]: %s\n", change.Path, change.Message)
		case 2: // Error
			errorCount++
		case 1: // Warning
			warningCount++
		case 0: // Info
			infoCount++
		}
	}

	// Summary by severity
	fmt.Printf("\nSummary:\n")
	fmt.Printf("- Critical (API will break): %d\n", criticalCount)
	fmt.Printf("- Errors (likely to break): %d\n", errorCount)
	fmt.Printf("- Warnings (may affect clients): %d\n", warningCount)
	fmt.Printf("- Info (non-breaking changes): %d\n", infoCount)

	// Check if changes are backward compatible
	if result.HasBreakingChanges {
		fmt.Println("\n⚠️  This update contains BREAKING CHANGES")
		fmt.Println("Consider versioning the API (e.g., /v2/...)")
	} else {
		fmt.Println("\n✓ Changes are backward compatible")
	}
}
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.ParseWithOptions(
		parser.WithFilePath("../testdata/petstore-v1.yaml"),
		parser.WithValidateStructure(true),
	)
	if err != nil {
		log.Fatal(err)
	}

	target, err := parser.ParseWithOptions(
		parser.WithFilePath("../testdata/petstore-v2.yaml"),
		parser.WithValidateStructure(true),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Compare parsed documents
	result, err := differ.DiffWithOptions(
		differ.WithSourceParsed(*source),
		differ.WithTargetParsed(*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 (Pipeline)

Example_pipeline demonstrates the diff → validate pipeline workflow using ToParseResult to chain operations on the target document.

package main

import (
	"fmt"
	"log"

	"github.com/erraggy/oastools/differ"
	"github.com/erraggy/oastools/parser"
)

func main() {
	// Parse both API versions
	v1, err := parser.ParseWithOptions(
		parser.WithFilePath("../testdata/petstore-v1.yaml"),
		parser.WithValidateStructure(true),
	)
	if err != nil {
		log.Fatal(err)
	}

	v2, err := parser.ParseWithOptions(
		parser.WithFilePath("../testdata/petstore-v2.yaml"),
		parser.WithValidateStructure(true),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Step 1: Compare versions and detect breaking changes
	diffResult, err := differ.DiffWithOptions(
		differ.WithSourceParsed(*v1),
		differ.WithTargetParsed(*v2),
		differ.WithMode(differ.ModeBreaking),
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Comparing %s → %s\n", diffResult.SourceVersion, diffResult.TargetVersion)
	fmt.Printf("Found %d changes (%d breaking)\n", len(diffResult.Changes), diffResult.BreakingCount)

	// Step 2: Convert to ParseResult for pipeline continuation
	// ToParseResult returns the TARGET (v2) document, enabling workflows like:
	// diff(v1, v2) → validate(v2) → publish
	parseResult := diffResult.ToParseResult()

	// The target document is ready for downstream operations
	fmt.Printf("Target document ready for processing: %s\n", parseResult.Version)

	// Changes from diff are preserved as warnings for visibility
	fmt.Printf("Diff changes preserved as %d warnings\n", len(parseResult.Warnings))
}
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() {
	result, err := differ.DiffWithOptions(
		differ.WithSourceFilePath("../testdata/petstore-v1.yaml"),
		differ.WithTargetFilePath("../testdata/petstore-v2.yaml"),
		differ.WithMode(differ.ModeSimple),
	)
	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

View Source
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 BreakingChangeRule added in v1.31.0

type BreakingChangeRule struct {
	// Severity overrides the default severity for this change type.
	// If nil, the default severity is used.
	Severity *Severity

	// Ignore completely ignores this change type (not included in results).
	Ignore bool
}

BreakingChangeRule configures how a specific change type is treated.

func (*BreakingChangeRule) ApplyRule added in v1.31.0

func (r *BreakingChangeRule) ApplyRule(defaultSeverity Severity) (Severity, bool)

ApplyRule applies a rule to the given default severity. Returns the (possibly overridden) severity and whether to ignore the change.

type BreakingRulesConfig added in v1.31.0

type BreakingRulesConfig struct {
	// Operation configures rules for operation-level changes
	Operation *OperationRules

	// Parameter configures rules for parameter changes
	Parameter *ParameterRules

	// RequestBody configures rules for request body changes
	RequestBody *RequestBodyRules

	// Response configures rules for response changes
	Response *ResponseRules

	// Schema configures rules for schema changes
	Schema *SchemaRules

	// Security configures rules for security scheme changes
	Security *SecurityRules

	// Server configures rules for server changes
	Server *ServerRules

	// Endpoint configures rules for endpoint (path) changes
	Endpoint *EndpointRules

	// Info configures rules for info object changes
	Info *InfoRules

	// Extension configures rules for extension (x-*) changes
	Extension *ExtensionRules
}

BreakingRulesConfig configures which changes are considered breaking and their severity levels. Use this to customize breaking change detection based on your organization's API compatibility policies.

Example:

rules := &differ.BreakingRulesConfig{
    Operation: &differ.OperationRules{
        OperationIDModified: &differ.BreakingChangeRule{
            Severity: differ.SeverityPtr(differ.SeverityInfo), // Not breaking for us
        },
    },
    Schema: &differ.SchemaRules{
        PropertyRemoved: &differ.BreakingChangeRule{Ignore: true}, // We handle this differently
    },
}
d := differ.New()
d.BreakingRules = rules

func DefaultRules added in v1.31.0

func DefaultRules() *BreakingRulesConfig

DefaultRules returns a BreakingRulesConfig with all default behaviors. This is equivalent to not setting any rules.

func LenientRules added in v1.31.0

func LenientRules() *BreakingRulesConfig

LenientRules returns a BreakingRulesConfig that treats fewer changes as breaking. This downgrades many errors to warnings.

func StrictRules added in v1.31.0

func StrictRules() *BreakingRulesConfig

StrictRules returns a BreakingRulesConfig that treats more changes as breaking. This elevates many warnings to errors.

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
	// Line is the 1-based line number in the source file (0 if unknown)
	Line int
	// Column is the 1-based column number in the source file (0 if unknown)
	Column int
	// File is the source file path (empty for main document)
	File string
}

Change represents a single difference between two OpenAPI specifications

func (Change) HasLocation added in v1.27.0

func (c Change) HasLocation() bool

HasLocation returns true if this change has source location information.

func (Change) Location added in v1.27.0

func (c Change) Location() string

Location returns an IDE-friendly location string. Returns "file:line:column" if file is set, "line:column" if only line is set, or the Path if location is unknown.

func (Change) String

func (c Change) String() string

String returns a formatted string representation of the change

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"
	// CategoryExtension indicates a specification extension (x-*) change
	CategoryExtension ChangeCategory = "extension"
)

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 DiffMode

type DiffMode int

DiffMode indicates the type of diff operation to perform

const (
	// ModeSimple reports all semantic differences between documents
	ModeSimple DiffMode = iota
	// ModeBreaking categorizes changes and identifies breaking API changes
	ModeBreaking
)

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
	// SourceStats contains statistical information about the source document
	SourceStats parser.DocumentStats
	// SourceSize is the size of the source document in bytes
	SourceSize int64
	// TargetVersion is the target document's OAS version string
	TargetVersion string
	// TargetOASVersion is the enumerated target OAS version
	TargetOASVersion parser.OASVersion
	// TargetStats contains statistical information about the target document
	TargetStats parser.DocumentStats
	// TargetSize is the size of the target document in bytes
	TargetSize int64
	// TargetDocument contains the target/right document for ToParseResult() chaining.
	// In a pipeline model, returning the target enables workflows like:
	// diff(v1, v2) → validate(v2) → publish
	TargetDocument any
	// TargetSourcePath is the target document's source path
	TargetSourcePath string
	// TargetSourceFormat is the target document's format (JSON or YAML)
	TargetSourceFormat parser.SourceFormat
	// 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 DiffWithOptions added in v1.11.0

func DiffWithOptions(opts ...Option) (*DiffResult, error)

DiffWithOptions compares two 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 := differ.DiffWithOptions(
    differ.WithSourceFilePath("api-v1.yaml"),
    differ.WithTargetFilePath("api-v2.yaml"),
    differ.WithMode(differ.ModeBreaking),
)

func (*DiffResult) ToParseResult added in v1.41.0

func (r *DiffResult) ToParseResult() *parser.ParseResult

ToParseResult converts the DiffResult to a ParseResult representing the target document. This enables pipeline workflows where the "right" (newer) document continues through the processing chain (e.g., diff → validate → publish). Changes are converted to string warnings with severity prefixes for filtering.

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
	// SourceMap provides source location lookup for the source document.
	// When set, changes will include Line, Column, and File information
	// for elements in the source document.
	SourceMap *parser.SourceMap
	// TargetMap provides source location lookup for the target document.
	// When set, changes will include Line, Column, and File information
	// for elements in the target document (used for additions).
	TargetMap *parser.SourceMap
	// BreakingRules configures which changes are considered breaking
	// and their severity levels. When nil, default rules are used.
	// See BreakingRulesConfig for configuration options.
	BreakingRules *BreakingRulesConfig
}

Differ handles OpenAPI specification comparison

func New

func New() *Differ

New creates a new Differ instance with default settings

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

type EndpointRules added in v1.31.0

type EndpointRules struct {
	// Removed configures the rule for when an endpoint is removed.
	// Default: SeverityCritical
	Removed *BreakingChangeRule

	// Added configures the rule for when an endpoint is added.
	// Default: SeverityInfo
	Added *BreakingChangeRule

	// DescriptionModified configures the rule for description changes.
	// Default: SeverityInfo
	DescriptionModified *BreakingChangeRule
}

EndpointRules configures rules for endpoint (path) changes.

type ExtensionRules added in v1.31.0

type ExtensionRules struct {
	// Removed configures the rule for when an extension is removed.
	// Default: SeverityInfo
	Removed *BreakingChangeRule

	// Added configures the rule for when an extension is added.
	// Default: SeverityInfo
	Added *BreakingChangeRule

	// Modified configures the rule for when an extension is modified.
	// Default: SeverityInfo
	Modified *BreakingChangeRule
}

ExtensionRules configures rules for specification extension (x-*) changes.

type InfoRules added in v1.31.0

type InfoRules struct {
	// TitleModified configures the rule for title changes.
	// Default: SeverityInfo
	TitleModified *BreakingChangeRule

	// VersionModified configures the rule for version changes.
	// Default: SeverityInfo
	VersionModified *BreakingChangeRule

	// DescriptionModified configures the rule for description changes.
	// Default: SeverityInfo
	DescriptionModified *BreakingChangeRule
}

InfoRules configures rules for info object changes.

type OperationRules added in v1.31.0

type OperationRules struct {
	// Removed configures the rule for when an operation is removed.
	// Default: SeverityCritical
	Removed *BreakingChangeRule

	// OperationIDModified configures the rule for operationId changes.
	// Default: SeverityWarning
	OperationIDModified *BreakingChangeRule

	// SummaryModified configures the rule for summary changes.
	// Default: SeverityInfo
	SummaryModified *BreakingChangeRule

	// DescriptionModified configures the rule for description changes.
	// Default: SeverityInfo
	DescriptionModified *BreakingChangeRule

	// DeprecatedModified configures the rule for deprecated flag changes.
	// Default: SeverityInfo
	DeprecatedModified *BreakingChangeRule

	// TagsModified configures the rule for operation tags changes.
	// Default: SeverityInfo
	TagsModified *BreakingChangeRule

	// Added configures the rule for when an operation is added.
	// Default: SeverityInfo
	Added *BreakingChangeRule
}

OperationRules configures rules for HTTP operation changes.

type Option added in v1.11.0

type Option func(*diffConfig) error

Option is a function that configures a diff operation

func WithBreakingRules added in v1.31.0

func WithBreakingRules(rules *BreakingRulesConfig) Option

WithBreakingRules configures which changes are considered breaking and their severity levels. This allows customizing breaking change detection based on your organization's API compatibility policies.

Example:

result, _ := differ.DiffWithOptions(
    differ.WithSourceFilePath("v1.yaml"),
    differ.WithTargetFilePath("v2.yaml"),
    differ.WithMode(differ.ModeBreaking),
    differ.WithBreakingRules(&differ.BreakingRulesConfig{
        Operation: &differ.OperationRules{
            OperationIDModified: &differ.BreakingChangeRule{
                Severity: differ.SeverityPtr(differ.SeverityInfo),
            },
        },
    }),
)

func WithIncludeInfo added in v1.11.0

func WithIncludeInfo(enabled bool) Option

WithIncludeInfo enables or disables informational changes Default: true

func WithMode added in v1.11.0

func WithMode(mode DiffMode) Option

WithMode sets the diff mode (Simple or Breaking) Default: ModeSimple

func WithSourceFilePath added in v1.11.0

func WithSourceFilePath(path string) Option

WithSourceFilePath specifies a file path or URL as the source document

func WithSourceMap added in v1.27.0

func WithSourceMap(sm *parser.SourceMap) Option

WithSourceMap provides a SourceMap for the source document. When set, changes will include Line, Column, and File information for elements that exist in (or were removed from) the source document.

func WithSourceParsed added in v1.11.0

func WithSourceParsed(result parser.ParseResult) Option

WithSourceParsed specifies a parsed ParseResult as the source document

func WithTargetFilePath added in v1.11.0

func WithTargetFilePath(path string) Option

WithTargetFilePath specifies a file path or URL as the target document

func WithTargetMap added in v1.27.0

func WithTargetMap(sm *parser.SourceMap) Option

WithTargetMap provides a SourceMap for the target document. When set, changes will include Line, Column, and File information for elements that were added in the target document.

func WithTargetParsed added in v1.11.0

func WithTargetParsed(result parser.ParseResult) Option

WithTargetParsed specifies a parsed ParseResult as the target document

func WithUserAgent added in v1.11.0

func WithUserAgent(ua string) Option

WithUserAgent sets the User-Agent string for HTTP requests Default: "" (uses parser default)

type ParameterRules added in v1.31.0

type ParameterRules struct {
	// Removed configures the rule for when a parameter is removed.
	// Default: SeverityError (required) / SeverityWarning (optional)
	Removed *BreakingChangeRule

	// Added configures the rule for when a parameter is added.
	// Default: SeverityError (required) / SeverityInfo (optional)
	Added *BreakingChangeRule

	// RequiredChanged configures the rule for required field changes.
	// Default: SeverityError (false→true) / SeverityInfo (true→false)
	RequiredChanged *BreakingChangeRule

	// TypeChanged configures the rule for type changes.
	// Default: SeverityError
	TypeChanged *BreakingChangeRule

	// FormatChanged configures the rule for format changes.
	// Default: SeverityWarning
	FormatChanged *BreakingChangeRule

	// StyleChanged configures the rule for style changes.
	// Default: SeverityWarning
	StyleChanged *BreakingChangeRule

	// SchemaChanged configures the rule for schema changes.
	// Default: varies by schema change
	SchemaChanged *BreakingChangeRule

	// DescriptionModified configures the rule for description changes.
	// Default: SeverityInfo
	DescriptionModified *BreakingChangeRule
}

ParameterRules configures rules for parameter changes.

type RequestBodyRules added in v1.31.0

type RequestBodyRules struct {
	// Removed configures the rule for when a request body is removed.
	// Default: SeverityError
	Removed *BreakingChangeRule

	// Added configures the rule for when a request body is added.
	// Default: SeverityError (required) / SeverityInfo (optional)
	Added *BreakingChangeRule

	// RequiredChanged configures the rule for required field changes.
	// Default: SeverityError (false→true) / SeverityInfo (true→false)
	RequiredChanged *BreakingChangeRule

	// MediaTypeRemoved configures the rule for when a media type is removed.
	// Default: SeverityWarning
	MediaTypeRemoved *BreakingChangeRule

	// MediaTypeAdded configures the rule for when a media type is added.
	// Default: SeverityInfo
	MediaTypeAdded *BreakingChangeRule

	// SchemaChanged configures the rule for schema changes.
	// Default: varies by schema change
	SchemaChanged *BreakingChangeRule
}

RequestBodyRules configures rules for request body changes.

type ResponseRules added in v1.31.0

type ResponseRules struct {
	// Removed configures the rule for when a response is removed.
	// Default: SeverityError (success codes) / SeverityWarning (error codes)
	Removed *BreakingChangeRule

	// Added configures the rule for when a response is added.
	// Default: SeverityInfo
	Added *BreakingChangeRule

	// DescriptionModified configures the rule for description changes.
	// Default: SeverityInfo
	DescriptionModified *BreakingChangeRule

	// MediaTypeRemoved configures the rule for when a media type is removed.
	// Default: SeverityWarning
	MediaTypeRemoved *BreakingChangeRule

	// MediaTypeAdded configures the rule for when a media type is added.
	// Default: SeverityInfo
	MediaTypeAdded *BreakingChangeRule

	// HeaderRemoved configures the rule for when a header is removed.
	// Default: SeverityWarning
	HeaderRemoved *BreakingChangeRule

	// HeaderAdded configures the rule for when a header is added.
	// Default: SeverityInfo
	HeaderAdded *BreakingChangeRule

	// SchemaChanged configures the rule for schema changes.
	// Default: varies by schema change
	SchemaChanged *BreakingChangeRule
}

ResponseRules configures rules for response changes.

type RuleKey added in v1.31.0

type RuleKey struct {
	Category   ChangeCategory
	ChangeType ChangeType
	SubType    string // Additional context (e.g., "operationId", "required")
}

RuleKey identifies a specific change type for rule lookup.

type SchemaRules added in v1.31.0

type SchemaRules struct {
	// Removed configures the rule for when a schema is removed.
	// Default: SeverityError
	Removed *BreakingChangeRule

	// Added configures the rule for when a schema is added.
	// Default: SeverityInfo
	Added *BreakingChangeRule

	// TypeChanged configures the rule for type changes.
	// Default: SeverityError
	TypeChanged *BreakingChangeRule

	// FormatChanged configures the rule for format changes.
	// Default: SeverityWarning
	FormatChanged *BreakingChangeRule

	// RequiredAdded configures the rule for when a required field is added.
	// Default: SeverityError
	RequiredAdded *BreakingChangeRule

	// RequiredRemoved configures the rule for when a required field is removed.
	// Default: SeverityInfo
	RequiredRemoved *BreakingChangeRule

	// PropertyRemoved configures the rule for when a property is removed.
	// Default: SeverityWarning
	PropertyRemoved *BreakingChangeRule

	// PropertyAdded configures the rule for when a property is added.
	// Default: SeverityInfo
	PropertyAdded *BreakingChangeRule

	// EnumValueRemoved configures the rule for when an enum value is removed.
	// Default: SeverityError
	EnumValueRemoved *BreakingChangeRule

	// EnumValueAdded configures the rule for when an enum value is added.
	// Default: SeverityInfo
	EnumValueAdded *BreakingChangeRule

	// MaximumDecreased configures the rule for when maximum is decreased.
	// Default: SeverityError
	MaximumDecreased *BreakingChangeRule

	// MinimumIncreased configures the rule for when minimum is increased.
	// Default: SeverityError
	MinimumIncreased *BreakingChangeRule

	// MaxLengthDecreased configures the rule for when maxLength is decreased.
	// Default: SeverityError
	MaxLengthDecreased *BreakingChangeRule

	// MinLengthIncreased configures the rule for when minLength is increased.
	// Default: SeverityWarning
	MinLengthIncreased *BreakingChangeRule

	// PatternChanged configures the rule for when pattern is changed.
	// Default: SeverityWarning
	PatternChanged *BreakingChangeRule

	// NullableRemoved configures the rule for when nullable is removed.
	// Default: SeverityError
	NullableRemoved *BreakingChangeRule

	// NullableAdded configures the rule for when nullable is added.
	// Default: SeverityInfo
	NullableAdded *BreakingChangeRule

	// AdditionalPropertiesChanged configures the rule for additionalProperties changes.
	// Default: SeverityWarning
	AdditionalPropertiesChanged *BreakingChangeRule

	// DescriptionModified configures the rule for description changes.
	// Default: SeverityInfo
	DescriptionModified *BreakingChangeRule
}

SchemaRules configures rules for schema changes.

type SecurityRules added in v1.31.0

type SecurityRules struct {
	// Removed configures the rule for when a security scheme is removed.
	// Default: SeverityError
	Removed *BreakingChangeRule

	// Added configures the rule for when a security scheme is added.
	// Default: SeverityWarning
	Added *BreakingChangeRule

	// TypeChanged configures the rule for type changes.
	// Default: SeverityError
	TypeChanged *BreakingChangeRule

	// ScopeRemoved configures the rule for when a scope is removed.
	// Default: SeverityWarning
	ScopeRemoved *BreakingChangeRule

	// ScopeAdded configures the rule for when a scope is added.
	// Default: SeverityInfo
	ScopeAdded *BreakingChangeRule
}

SecurityRules configures rules for security scheme changes.

type ServerRules added in v1.31.0

type ServerRules struct {
	// Removed configures the rule for when a server is removed.
	// Default: SeverityWarning
	Removed *BreakingChangeRule

	// Added configures the rule for when a server is added.
	// Default: SeverityInfo
	Added *BreakingChangeRule

	// DescriptionModified configures the rule for description changes.
	// Default: SeverityInfo
	DescriptionModified *BreakingChangeRule

	// VariableChanged configures the rule for server variable changes.
	// Default: SeverityWarning
	VariableChanged *BreakingChangeRule
}

ServerRules configures rules for server changes.

type Severity

type Severity = severity.Severity

Severity indicates the severity level of a change

func SeverityPtr added in v1.31.0

func SeverityPtr(s Severity) *Severity

SeverityPtr is a helper function to create a pointer to a Severity value. This is useful when configuring BreakingChangeRule.Severity.

Jump to

Keyboard shortcuts

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