Separate projection cloning and value comparison
This commit is contained in:
110
document_compare.go
Normal file
110
document_compare.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package configkit
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type documentDeltaKind uint8
|
||||
|
||||
const (
|
||||
documentAdded documentDeltaKind = iota + 1
|
||||
documentRemoved
|
||||
documentReplaced
|
||||
)
|
||||
|
||||
type documentDelta struct {
|
||||
path Path
|
||||
kind documentDeltaKind
|
||||
}
|
||||
|
||||
// compareConfigurationValues ignores locations and sensitivity metadata. Mapping
|
||||
// order is immaterial; sequences are atomic for change reporting.
|
||||
func compareConfigurationValues(before, after *document.Node) []documentDelta {
|
||||
var deltas []documentDelta
|
||||
compareConfigurationValue(before, after, Path{}, &deltas)
|
||||
slices.SortFunc(deltas, func(left, right documentDelta) int {
|
||||
if order := cmp.Compare(left.path.String(), right.path.String()); order != 0 {
|
||||
return order
|
||||
}
|
||||
return int(left.kind) - int(right.kind)
|
||||
})
|
||||
return deltas
|
||||
}
|
||||
|
||||
func compareConfigurationValue(before, after *document.Node, path Path, deltas *[]documentDelta) {
|
||||
if before == nil && after == nil {
|
||||
return
|
||||
}
|
||||
if before == nil {
|
||||
*deltas = append(*deltas, documentDelta{path: path.clone(), kind: documentAdded})
|
||||
return
|
||||
}
|
||||
if after == nil {
|
||||
*deltas = append(*deltas, documentDelta{path: path.clone(), kind: documentRemoved})
|
||||
return
|
||||
}
|
||||
if before.Kind != after.Kind {
|
||||
*deltas = append(*deltas, documentDelta{path: path.clone(), kind: documentReplaced})
|
||||
return
|
||||
}
|
||||
switch before.Kind {
|
||||
case document.Mapping:
|
||||
beforeEntries := make(map[string]*document.Node, len(before.Mapping))
|
||||
afterEntries := make(map[string]*document.Node, len(after.Mapping))
|
||||
keys := make(map[string]bool, len(before.Mapping)+len(after.Mapping))
|
||||
for _, entry := range before.Mapping {
|
||||
beforeEntries[entry.Key] = entry.Value
|
||||
keys[entry.Key] = true
|
||||
}
|
||||
for _, entry := range after.Mapping {
|
||||
afterEntries[entry.Key] = entry.Value
|
||||
keys[entry.Key] = true
|
||||
}
|
||||
ordered := make([]string, 0, len(keys))
|
||||
for key := range keys {
|
||||
ordered = append(ordered, key)
|
||||
}
|
||||
slices.Sort(ordered)
|
||||
for _, key := range ordered {
|
||||
compareConfigurationValue(beforeEntries[key], afterEntries[key], path.Key(key), deltas)
|
||||
}
|
||||
case document.Sequence:
|
||||
if !configurationValuesEqual(before, after) {
|
||||
*deltas = append(*deltas, documentDelta{path: path.clone(), kind: documentReplaced})
|
||||
}
|
||||
default:
|
||||
if before.Text != after.Text {
|
||||
*deltas = append(*deltas, documentDelta{path: path.clone(), kind: documentReplaced})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// configurationValuesEqual compares only configuration values, not provenance or
|
||||
// sensitivity. Callers auditing validation must check sensitivity separately.
|
||||
func configurationValuesEqual(left, right *document.Node) bool {
|
||||
if left == nil || right == nil {
|
||||
return left == right
|
||||
}
|
||||
if left.Kind != right.Kind || left.Text != right.Text || len(left.Mapping) != len(right.Mapping) || len(left.Sequence) != len(right.Sequence) {
|
||||
return false
|
||||
}
|
||||
if left.Kind == document.Mapping {
|
||||
leftMap := make(map[string]*document.Node, len(left.Mapping))
|
||||
for _, entry := range left.Mapping {
|
||||
leftMap[entry.Key] = entry.Value
|
||||
}
|
||||
for _, entry := range right.Mapping {
|
||||
if !configurationValuesEqual(leftMap[entry.Key], entry.Value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := range left.Sequence {
|
||||
if !configurationValuesEqual(left.Sequence[i], right.Sequence[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user