go-build/pkg/sdk/diff.go
Snider d0e22f76e1 refactor: replace remaining fmt.Errorf/errors.New with coreerr.E()
Replace all fmt.Errorf and errors.New calls across 32 production files
with coreerr.E("caller.Method", "message", err) from go-log, matching
the convention already established in publishers/scoop.go and
publishers/homebrew.go.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-16 21:03:21 +00:00

85 lines
2.3 KiB
Go

package sdk
import (
"fmt"
coreerr "forge.lthn.ai/core/go-log"
"github.com/getkin/kin-openapi/openapi3"
"github.com/oasdiff/oasdiff/checker"
"github.com/oasdiff/oasdiff/diff"
"github.com/oasdiff/oasdiff/load"
)
// DiffResult holds the result of comparing two OpenAPI specs.
type DiffResult struct {
// Breaking is true if breaking changes were detected.
Breaking bool
// Changes is the list of breaking changes.
Changes []string
// Summary is a human-readable summary.
Summary string
}
// Diff compares two OpenAPI specs and detects breaking changes.
func Diff(basePath, revisionPath string) (*DiffResult, error) {
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
// Load specs
baseSpec, err := load.NewSpecInfo(loader, load.NewSource(basePath))
if err != nil {
return nil, coreerr.E("sdk.Diff", "failed to load base spec", err)
}
revSpec, err := load.NewSpecInfo(loader, load.NewSource(revisionPath))
if err != nil {
return nil, coreerr.E("sdk.Diff", "failed to load revision spec", err)
}
// Compute diff with operations sources map for better error reporting
diffResult, operationsSources, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), baseSpec, revSpec)
if err != nil {
return nil, coreerr.E("sdk.Diff", "failed to compute diff", err)
}
// Check for breaking changes
config := checker.NewConfig(checker.GetAllChecks())
breaks := checker.CheckBackwardCompatibilityUntilLevel(
config,
diffResult,
operationsSources,
checker.ERR, // Only errors (breaking changes)
)
// Build result
result := &DiffResult{
Breaking: len(breaks) > 0,
Changes: make([]string, 0, len(breaks)),
}
localizer := checker.NewDefaultLocalizer()
for _, b := range breaks {
// GetUncolorizedText uses US spelling — upstream oasdiff API.
result.Changes = append(result.Changes, b.GetUncolorizedText(localizer))
}
if result.Breaking {
result.Summary = fmt.Sprintf("%d breaking change(s) detected", len(breaks))
} else {
result.Summary = "No breaking changes"
}
return result, nil
}
// DiffExitCode returns the exit code for CI integration.
// 0 = no breaking changes, 1 = breaking changes, 2 = error
func DiffExitCode(result *DiffResult, err error) int {
if err != nil {
return 2
}
if result.Breaking {
return 1
}
return 0
}