235 lines
7.2 KiB
Go
235 lines
7.2 KiB
Go
package configkit
|
|
|
|
import (
|
|
"slices"
|
|
|
|
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
)
|
|
|
|
func metadataFromDecoded(decoded decodedConfiguration) Metadata {
|
|
metadata := Metadata{
|
|
fields: make(map[string]FieldInfo),
|
|
history: make(map[string][]Change),
|
|
}
|
|
baseline := make(map[string]*document.Node)
|
|
indexDocument(decoded.baseline, Path{}, baseline)
|
|
for pointer := range baseline {
|
|
path, err := ParsePath(pointer)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
metadata.history[pointer] = append(metadata.history[pointer], Change{
|
|
Path: path, Origin: Origin{Layer: LayerDefault},
|
|
})
|
|
}
|
|
for pointer := range decoded.presence {
|
|
path, err := ParsePath(pointer)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
origin := cloneOrigin(decoded.origins[pointer])
|
|
change := Change{Path: path, Origin: origin}
|
|
if _, ok := baseline[pointer]; ok {
|
|
change.Previous = []Origin{{Layer: LayerDefault}}
|
|
}
|
|
metadata.history[pointer] = append(metadata.history[pointer], change)
|
|
}
|
|
collectFieldInfo(decoded.final, Path{}, decoded, metadata.fields)
|
|
return metadata
|
|
}
|
|
|
|
func applyOverlayMetadata(metadata *Metadata, records []overlayRecord) {
|
|
for _, record := range records {
|
|
for _, displaced := range record.displaced {
|
|
pointer := displaced.path.String()
|
|
history := metadata.history[pointer]
|
|
profileIndex := -1
|
|
for index := len(history) - 1; index >= 0; index-- {
|
|
if history[index].Origin.Layer == LayerProfile {
|
|
profileIndex = index
|
|
break
|
|
}
|
|
}
|
|
baseChange := Change{Path: displaced.path.clone(), Origin: cloneOrigin(displaced.origin)}
|
|
if len(history) != 0 && history[len(history)-1].Origin.Layer == LayerDefault {
|
|
baseChange.Previous = []Origin{cloneOrigin(history[len(history)-1].Origin)}
|
|
}
|
|
if profileIndex >= 0 {
|
|
history = append(history[:profileIndex], append([]Change{baseChange}, history[profileIndex:]...)...)
|
|
} else {
|
|
history = append(history, baseChange)
|
|
}
|
|
|
|
change := cloneChange(record.change)
|
|
change.Previous = []Origin{cloneOrigin(displaced.origin)}
|
|
if profileIndex >= 0 {
|
|
// The effective-input projection created a leaf-shaped profile
|
|
// record. Replace it with the actual parent operation.
|
|
profileIndex++
|
|
history[profileIndex] = change
|
|
} else {
|
|
history = append(history, change)
|
|
}
|
|
metadata.history[pointer] = history
|
|
}
|
|
}
|
|
}
|
|
|
|
func collectFieldInfo(node *document.Node, path Path, decoded decodedConfiguration, fields map[string]FieldInfo) {
|
|
pointer := path.String()
|
|
_, explicit := decoded.presence[pointer]
|
|
fields[pointer] = FieldInfo{
|
|
Explicit: explicit,
|
|
Null: node.Kind == document.Null,
|
|
Origin: cloneOrigin(decoded.origins[pointer]),
|
|
}
|
|
switch node.Kind {
|
|
case document.Mapping:
|
|
for _, entry := range node.Mapping {
|
|
collectFieldInfo(entry.Value, path.Key(entry.Key), decoded, fields)
|
|
}
|
|
case document.Sequence:
|
|
for index, child := range node.Sequence {
|
|
collectFieldInfo(child, path.Index(index), decoded, fields)
|
|
}
|
|
}
|
|
}
|
|
|
|
func indexDocument(node *document.Node, path Path, index map[string]*document.Node) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
index[path.String()] = node
|
|
switch node.Kind {
|
|
case document.Mapping:
|
|
for _, entry := range node.Mapping {
|
|
indexDocument(entry.Value, path.Key(entry.Key), index)
|
|
}
|
|
case document.Sequence:
|
|
for position, child := range node.Sequence {
|
|
indexDocument(child, path.Index(position), index)
|
|
}
|
|
}
|
|
}
|
|
|
|
func cloneMetadata(metadata Metadata) Metadata {
|
|
if metadata.fields == nil && metadata.history == nil {
|
|
return Metadata{}
|
|
}
|
|
cloned := Metadata{
|
|
fields: make(map[string]FieldInfo, len(metadata.fields)),
|
|
history: make(map[string][]Change, len(metadata.history)),
|
|
}
|
|
for path, info := range metadata.fields {
|
|
cloned.fields[path] = cloneFieldInfo(info)
|
|
}
|
|
for path, changes := range metadata.history {
|
|
clonedChanges := make([]Change, len(changes))
|
|
for index, change := range changes {
|
|
clonedChanges[index] = cloneChange(change)
|
|
}
|
|
cloned.history[path] = clonedChanges
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
func applyResolutionMetadata(metadata *Metadata, resolved resolutionResult) {
|
|
for _, snapshot := range resolved.snapshots {
|
|
pointer := snapshot.change.Path.String()
|
|
change := cloneChange(snapshot.change)
|
|
if info, ok := metadata.fields[pointer]; ok {
|
|
change.Previous = []Origin{cloneOrigin(info.Origin)}
|
|
info.Origin = cloneOrigin(change.Origin)
|
|
metadata.fields[pointer] = info
|
|
}
|
|
metadata.history[pointer] = append(metadata.history[pointer], change)
|
|
}
|
|
appendResolutionDescendantHistory(metadata, resolved)
|
|
for _, delta := range resolved.deltas {
|
|
record := coveringResolutionSnapshot(resolved.snapshots, delta.path)
|
|
if record == nil {
|
|
continue
|
|
}
|
|
for pointer, info := range metadata.fields {
|
|
path, err := ParsePath(pointer)
|
|
if err != nil || !pathContains(delta.path, path) {
|
|
continue
|
|
}
|
|
info.Origin = cloneOrigin(record.change.Origin)
|
|
metadata.fields[pointer] = info
|
|
}
|
|
}
|
|
refreshResolutionFields(metadata.fields, resolved.final, resolved.snapshots)
|
|
}
|
|
|
|
func appendResolutionDescendantHistory(metadata *Metadata, resolved resolutionResult) {
|
|
finalPaths := make(map[string]*document.Node)
|
|
indexDocument(resolved.final, Path{}, finalPaths)
|
|
for _, delta := range resolved.deltas {
|
|
record := coveringResolutionSnapshot(resolved.snapshots, delta.path)
|
|
if record == nil {
|
|
continue
|
|
}
|
|
affected := make(map[string]bool)
|
|
for pointer := range metadata.history {
|
|
if path, err := ParsePath(pointer); err == nil && pathContains(delta.path, path) {
|
|
affected[pointer] = true
|
|
}
|
|
}
|
|
for pointer := range finalPaths {
|
|
if path, err := ParsePath(pointer); err == nil && pathContains(delta.path, path) {
|
|
affected[pointer] = true
|
|
}
|
|
}
|
|
pointers := make([]string, 0, len(affected))
|
|
for pointer := range affected {
|
|
if pointer != record.change.Path.String() {
|
|
pointers = append(pointers, pointer)
|
|
}
|
|
}
|
|
slices.Sort(pointers)
|
|
for _, pointer := range pointers {
|
|
change := cloneChange(record.change)
|
|
if info, ok := metadata.fields[pointer]; ok {
|
|
change.Previous = []Origin{cloneOrigin(info.Origin)}
|
|
}
|
|
metadata.history[pointer] = append(metadata.history[pointer], change)
|
|
}
|
|
}
|
|
}
|
|
|
|
func coveringResolutionSnapshot(snapshots []resolutionSnapshot, path Path) *resolutionSnapshot {
|
|
var covering *resolutionSnapshot
|
|
for index := range snapshots {
|
|
snapshot := &snapshots[index]
|
|
if pathContains(snapshot.change.Path, path) && (covering == nil || len(snapshot.change.Path.segments) > len(covering.change.Path.segments)) {
|
|
covering = snapshot
|
|
}
|
|
}
|
|
return covering
|
|
}
|
|
|
|
func refreshResolutionFields(fields map[string]FieldInfo, final *document.Node, snapshots []resolutionSnapshot) {
|
|
finalPaths := make(map[string]*document.Node)
|
|
indexDocument(final, Path{}, finalPaths)
|
|
for pointer := range fields {
|
|
if _, ok := finalPaths[pointer]; !ok {
|
|
delete(fields, pointer)
|
|
}
|
|
}
|
|
for pointer, node := range finalPaths {
|
|
if info, ok := fields[pointer]; ok {
|
|
info.Null = node.Kind == document.Null
|
|
fields[pointer] = info
|
|
continue
|
|
}
|
|
origin := Origin{Layer: LayerResolution}
|
|
if path, err := ParsePath(pointer); err == nil {
|
|
if covering := coveringResolutionSnapshot(snapshots, path); covering != nil {
|
|
origin = cloneOrigin(covering.change.Origin)
|
|
}
|
|
}
|
|
fields[pointer] = FieldInfo{Null: node.Kind == document.Null, Origin: origin}
|
|
}
|
|
}
|