Files
ConfigKit/internal/engine/diff.go

99 lines
3.0 KiB
Go

package engine
import (
"bytes"
"errors"
"io"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
yamladapter "gitea.maximumdirect.net/eric/ConfigKit/internal/yaml"
)
var errDiffInspectionUnavailable = errors.New("configkit: diff inspection unavailable")
var errDiffSnapshotUnavailable = errors.New("configkit: diff snapshot unavailable")
// Difference describes one canonical changed region with safe snapshots.
type Difference struct {
Path Path
Left ValueSnapshot
Right ValueSnapshot
HiddenChanged bool
}
type diffRegion struct {
path Path
left *document.Node
right *document.Node
}
func prepareDiffRegions(left, right *Inspection) ([]diffRegion, error) {
if left == nil || left.final == nil || right == nil || right.final == nil {
return nil, errDiffInspectionUnavailable
}
deltas := compareConfigurationValues(left.final, right.final)
regions := make([]diffRegion, 0, len(deltas))
for _, delta := range deltas {
leftNode, leftPresent := documentAtPathWithLookup(left.final, delta.path, left.immutableLookup)
if !leftPresent {
leftNode = nil
}
rightNode, rightPresent := documentAtPathWithLookup(right.final, delta.path, right.immutableLookup)
if !rightPresent {
rightNode = nil
}
regions = append(regions, diffRegion{path: delta.path, left: leftNode, right: rightNode})
}
return regions, nil
}
// Diff compares the final frozen values retained by two successful inspections.
// Values are serialized only after applying the combined sensitivity of both loads.
func Diff(left, right *Inspection) ([]Difference, error) {
regions, err := prepareDiffRegions(left, right)
if err != nil {
return nil, err
}
sensitivity := newDiffSensitivity(left.secrets, right.secrets)
differences := make([]Difference, 0, len(regions))
for _, region := range regions {
paired, supported := preparePairedDiffSnapshot(region.left, region.right, region.path, sensitivity)
if !supported {
return nil, errDiffSnapshotUnavailable
}
leftSnapshot, err := serializeDiffSnapshot(paired.left, paired.leftRedacted)
if err != nil {
return nil, errDiffSnapshotUnavailable
}
rightSnapshot, err := serializeDiffSnapshot(paired.right, paired.rightRedacted)
if err != nil {
return nil, errDiffSnapshotUnavailable
}
differences = append(differences, Difference{
Path: region.path,
Left: leftSnapshot,
Right: rightSnapshot,
HiddenChanged: paired.hiddenChanged,
})
}
return differences, nil
}
func serializeDiffSnapshot(node *document.Node, redacted bool) (ValueSnapshot, error) {
if node == nil {
return ValueSnapshot{}, nil
}
var output bytes.Buffer
if err := writeDiffDocument(&output, node); err != nil {
return ValueSnapshot{}, errDiffSnapshotUnavailable
}
return ValueSnapshot{Present: true, Redacted: redacted, YAML: output.String()}, nil
}
func writeDiffDocument(output io.Writer, node *document.Node) error {
if err := yamladapter.Write(output, node); err != nil {
return errDiffSnapshotUnavailable
}
return nil
}