141 lines
3.5 KiB
Go
141 lines
3.5 KiB
Go
package confmgr
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// ChangeStatus classifies a single diff entry.
|
|
type ChangeStatus string
|
|
|
|
const (
|
|
StatusAdded ChangeStatus = "added"
|
|
StatusRemoved ChangeStatus = "removed"
|
|
StatusChanged ChangeStatus = "changed"
|
|
StatusUnchanged ChangeStatus = "unchanged"
|
|
)
|
|
|
|
// SetDiffEntry is one parameter-level difference between two config sets.
|
|
type SetDiffEntry struct {
|
|
Key string `json:"key"`
|
|
Status ChangeStatus `json:"status"`
|
|
Left *Parameter `json:"left,omitempty"`
|
|
Right *Parameter `json:"right,omitempty"`
|
|
}
|
|
|
|
// InstanceDiffEntry is one value-level difference between two config instances.
|
|
type InstanceDiffEntry struct {
|
|
Key string `json:"key"`
|
|
Status ChangeStatus `json:"status"`
|
|
Left any `json:"left,omitempty"`
|
|
Right any `json:"right,omitempty"`
|
|
}
|
|
|
|
// DiffSets compares two config sets parameter-by-parameter (keyed by Key),
|
|
// returning entries sorted by key. Both sides are included so the frontend can
|
|
// render either unified or side-by-side.
|
|
func DiffSets(left, right ConfigSet) []SetDiffEntry {
|
|
keys := map[string]bool{}
|
|
li := map[string]Parameter{}
|
|
ri := map[string]Parameter{}
|
|
for _, p := range left.Parameters {
|
|
li[p.Key] = p
|
|
keys[p.Key] = true
|
|
}
|
|
for _, p := range right.Parameters {
|
|
ri[p.Key] = p
|
|
keys[p.Key] = true
|
|
}
|
|
out := make([]SetDiffEntry, 0, len(keys))
|
|
for k := range keys {
|
|
l, lok := li[k]
|
|
r, rok := ri[k]
|
|
e := SetDiffEntry{Key: k}
|
|
switch {
|
|
case lok && !rok:
|
|
e.Status = StatusRemoved
|
|
lp := l
|
|
e.Left = &lp
|
|
case !lok && rok:
|
|
e.Status = StatusAdded
|
|
rp := r
|
|
e.Right = &rp
|
|
default:
|
|
lp, rp := l, r
|
|
e.Left, e.Right = &lp, &rp
|
|
if paramEqual(l, r) {
|
|
e.Status = StatusUnchanged
|
|
} else {
|
|
e.Status = StatusChanged
|
|
}
|
|
}
|
|
out = append(out, e)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
|
|
return out
|
|
}
|
|
|
|
// DiffInstances compares two config instances value-by-value (keyed by
|
|
// parameter key), returning entries sorted by key.
|
|
func DiffInstances(left, right ConfigInstance) []InstanceDiffEntry {
|
|
keys := map[string]bool{}
|
|
for k := range left.Values {
|
|
keys[k] = true
|
|
}
|
|
for k := range right.Values {
|
|
keys[k] = true
|
|
}
|
|
out := make([]InstanceDiffEntry, 0, len(keys))
|
|
for k := range keys {
|
|
lv, lok := left.Values[k]
|
|
rv, rok := right.Values[k]
|
|
e := InstanceDiffEntry{Key: k, Left: lv, Right: rv}
|
|
switch {
|
|
case lok && !rok:
|
|
e.Status = StatusRemoved
|
|
case !lok && rok:
|
|
e.Status = StatusAdded
|
|
case valueEqual(lv, rv):
|
|
e.Status = StatusUnchanged
|
|
default:
|
|
e.Status = StatusChanged
|
|
}
|
|
out = append(out, e)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
|
|
return out
|
|
}
|
|
|
|
func paramEqual(a, b Parameter) bool {
|
|
if a.Label != b.Label || a.Group != b.Group || a.Subgroup != b.Subgroup ||
|
|
a.DS != b.DS || a.Signal != b.Signal || a.Type != b.Type ||
|
|
a.Mandatory != b.Mandatory || a.Unit != b.Unit || a.Description != b.Description {
|
|
return false
|
|
}
|
|
if !valueEqual(a.Default, b.Default) || !floatPtrEqual(a.Min, b.Min) || !floatPtrEqual(a.Max, b.Max) {
|
|
return false
|
|
}
|
|
if len(a.EnumValues) != len(b.EnumValues) {
|
|
return false
|
|
}
|
|
for i := range a.EnumValues {
|
|
if a.EnumValues[i] != b.EnumValues[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func floatPtrEqual(a, b *float64) bool {
|
|
if a == nil || b == nil {
|
|
return a == b
|
|
}
|
|
return *a == *b
|
|
}
|
|
|
|
// valueEqual compares two JSON-decoded scalar values for diff purposes by
|
|
// rendering them to a canonical string.
|
|
func valueEqual(a, b any) bool {
|
|
return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
|
|
}
|