111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
package engine
|
|
|
|
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
|
|
}
|