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 }